Xamarin.Forms Bancos de dados locais
O mecanismo de banco de dados SQLite permite que Xamarin.Forms os aplicativos carreguem e salvem objetos de dados em código compartilhado. O aplicativo de exemplo usa uma tabela de banco de dados SQLite para armazenar itens pendentes. Este artigo descreve como usar SQLite.Net em código compartilhado para armazenar e recuperar informações em um banco de dados local.
Integre SQLite.NET em aplicativos móveis seguindo estas etapas:
- Instale o pacote NuGet.
- Configurar constantes.
- Crie uma classe de acesso de banco de dados.
- Acessar dados no Xamarin.Forms.
- Configuração avançada.
Instalar o pacote NuGet do SQLite
Use o gerenciador de pacotes NuGet para pesquisar sqlite-net-pcl e adicionar a versão mais recente ao projeto de código compartilhado.
Há diversos pacotes NuGet com nomes semelhantes. O pacote correto tem estes atributos:
- ID: sqlite-net-pcl
- Autores: SQLite-net
- Proprietários: praeclarum
- Link do NuGet:sqlite-net-pcl
Apesar do nome do pacote, use o pacote NuGet sqlite-net-pcl, mesmo em projetos do .NET Standard.
Importante
SQLite.NET é uma biblioteca de terceiros com suporte do repositório praeclarum/sqlite-net.
Configurar constantes de aplicativo
O projeto de exemplo inclui um arquivo Constants.cs que fornece dados comuns de configuração:
public static class Constants
{
public const string DatabaseFilename = "TodoSQLite.db3";
public const SQLite.SQLiteOpenFlags Flags =
// open the database in read/write mode
SQLite.SQLiteOpenFlags.ReadWrite |
// create the database if it doesn't exist
SQLite.SQLiteOpenFlags.Create |
// enable multi-threaded database access
SQLite.SQLiteOpenFlags.SharedCache;
public static string DatabasePath
{
get
{
var basePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
return Path.Combine(basePath, DatabaseFilename);
}
}
}
O arquivo de constantes especifica valores de enumeração padrão SQLiteOpenFlag
que são usados para inicializar a conexão de banco de dados. A SQLiteOpenFlag
enumeração dá suporte a esses valores:
Create
: a conexão criará automaticamente o arquivo de banco de dados se ele não existir.FullMutex
: a conexão é aberta no modo de threading serializado.NoMutex
: a conexão é aberta no modo multi-threading.PrivateCache
: a conexão não participará do cache compartilhado, mesmo que esteja habilitada.ReadWrite
: a conexão pode ler e gravar dados.SharedCache
: a conexão participará do cache compartilhado, se estiver habilitado.ProtectionComplete
: o arquivo é criptografado e inacessível enquanto o dispositivo está bloqueado.ProtectionCompleteUnlessOpen
: o arquivo é criptografado até ser aberto, mas pode ser acessado mesmo se o usuário bloquear o dispositivo.ProtectionCompleteUntilFirstUserAuthentication
: o arquivo é criptografado até que o usuário tenha inicializado e desbloqueado o dispositivo.ProtectionNone
: o arquivo de banco de dados não está criptografado.
Talvez seja necessário especificar sinalizadores diferentes dependendo de como seu banco de dados será usado. Para obter mais informações sobre SQLiteOpenFlags
, consulte Abrindo uma nova conexão de banco de dados no sqlite.org.
Criar uma classe de acesso ao banco de dados
Uma classe wrapper de banco de dados abstrai a camada de acesso a dados do restante do aplicativo. Essa classe centraliza a lógica de consulta e simplifica o gerenciamento da inicialização do banco de dados, facilitando a refatoração ou a expansão das operações de dados à medida que o aplicativo cresce. O aplicativo Todo define uma TodoItemDatabase
classe para essa finalidade.
Inicialização lenta
O TodoItemDatabase
usa a inicialização lenta assíncrona, representada pela classe personalizada AsyncLazy<T>
, para atrasar a inicialização do banco de dados até que ele seja acessado pela primeira vez:
public class TodoItemDatabase
{
static SQLiteAsyncConnection Database;
public static readonly AsyncLazy<TodoItemDatabase> Instance = new AsyncLazy<TodoItemDatabase>(async () =>
{
var instance = new TodoItemDatabase();
CreateTableResult result = await Database.CreateTableAsync<TodoItem>();
return instance;
});
public TodoItemDatabase()
{
Database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
}
//...
}
O Instance
campo é usado para criar a tabela de banco de dados para o TodoItem
objeto, se ele ainda não existir e retornará um TodoItemDatabase
como singleton. O Instance
campo do tipo AsyncLazy<TodoItemDatabase>
é construído na primeira vez que é aguardado. Se vários threads tentarem acessar o campo simultaneamente, todos eles usarão a construção única. Em seguida, quando a construção for concluída, todas as await
operações são concluídas. Além disso, todas as await
operações após a conclusão da construção continuam imediatamente, pois o valor está disponível.
Observação
A conexão de banco de dados é um campo estático que garante que uma única conexão de banco de dados seja usada durante a vida útil do aplicativo. O uso de uma conexão estática persistente oferece melhor desempenho do que abrir e fechar conexões várias vezes durante uma única sessão de aplicativo.
Inicialização lenta assíncrona
Para iniciar a inicialização do banco de dados, evitar o bloqueio da execução e ter a oportunidade de capturar exceções, o aplicativo de exemplo usa a initalização lenta assíncrona, representada pela AsyncLazy<T>
classe :
public class AsyncLazy<T>
{
readonly Lazy<Task<T>> instance;
public AsyncLazy(Func<T> factory)
{
instance = new Lazy<Task<T>>(() => Task.Run(factory));
}
public AsyncLazy(Func<Task<T>> factory)
{
instance = new Lazy<Task<T>>(() => Task.Run(factory));
}
public TaskAwaiter<T> GetAwaiter()
{
return instance.Value.GetAwaiter();
}
}
A AsyncLazy
classe combina os Lazy<T>
tipos e Task<T>
para criar uma tarefa inicializada lenta que representa a inicialização de um recurso. O delegado de fábrica que é passado para o construtor pode ser síncrono ou assíncrono. Os delegados de fábrica serão executados em um thread do pool de threads e não serão executados mais de uma vez (mesmo quando vários threads tentarem iniciá-los simultaneamente). Quando um delegado de fábrica é concluído, o valor inicializado lentamente está disponível e todos os métodos que aguardam a AsyncLazy<T>
instância recebem o valor. Para obter mais informações, consulte AsyncLazy.
Métodos de manipulação de dados
A TodoItemDatabase
classe inclui métodos para os quatro tipos de manipulação de dados: criar, ler, editar e excluir. A biblioteca SQLite.NET fornece um ORM (Mapa Relacional de Objeto) simples que permite armazenar e recuperar objetos sem gravar instruções SQL.
public class TodoItemDatabase
{
// ...
public Task<List<TodoItem>> GetItemsAsync()
{
return Database.Table<TodoItem>().ToListAsync();
}
public Task<List<TodoItem>> GetItemsNotDoneAsync()
{
// SQL queries are also possible
return Database.QueryAsync<TodoItem>("SELECT * FROM [TodoItem] WHERE [Done] = 0");
}
public Task<TodoItem> GetItemAsync(int id)
{
return Database.Table<TodoItem>().Where(i => i.ID == id).FirstOrDefaultAsync();
}
public Task<int> SaveItemAsync(TodoItem item)
{
if (item.ID != 0)
{
return Database.UpdateAsync(item);
}
else
{
return Database.InsertAsync(item);
}
}
public Task<int> DeleteItemAsync(TodoItem item)
{
return Database.DeleteAsync(item);
}
}
Acessar dados em Xamarin.Forms
A TodoItemDatabase
classe expõe o Instance
campo, por meio do qual as operações de acesso a dados na TodoItemDatabase
classe podem ser invocadas:
async void OnSaveClicked(object sender, EventArgs e)
{
var todoItem = (TodoItem)BindingContext;
TodoItemDatabase database = await TodoItemDatabase.Instance;
await database.SaveItemAsync(todoItem);
// Navigate backwards
await Navigation.PopAsync();
}
Configuração avançada
O SQLite fornece uma API robusta com mais recursos do que os abordados neste artigo e no aplicativo de exemplo. As seções a seguir abordam recursos importantes para escalabilidade.
Para obter mais informações, consulte Documentação do SQLite sobre sqlite.org.
Registro em log de gravação antecipada
Por padrão, o SQLite usa um diário de reversão tradicional. Uma cópia do conteúdo do banco de dados inalterado é gravada em um arquivo de reversão separado e, em seguida, as alterações são gravadas diretamente no arquivo de banco de dados. O COMMIT ocorre quando o diário de reversão é excluído.
Write-Ahead Logging (WAL) grava as alterações em um arquivo WAL separado primeiro. No modo WAL, um COMMIT é um registro especial, acrescentado ao arquivo WAL, que permite que várias transações ocorram em um único arquivo WAL. Um arquivo WAL é mesclado de volta ao arquivo de banco de dados em uma operação especial chamada ponto de verificação.
O WAL pode ser mais rápido para bancos de dados locais porque leitores e gravadores não bloqueiam uns aos outros, permitindo que as operações de leitura e gravação sejam simultâneas. No entanto, o modo WAL não permite alterações no tamanho da página, adiciona associações de arquivos adicionais ao banco de dados e adiciona a operação de ponto de verificação extra.
Para habilitar o WAL no SQLite.NET, chame o EnableWriteAheadLoggingAsync
método na SQLiteAsyncConnection
instância:
await Database.EnableWriteAheadLoggingAsync();
Para obter mais informações, consulte SQLite Write-Ahead Log on sqlite.org.
Copiar um banco de dados
Há vários casos em que pode ser necessário copiar um banco de dados SQLite:
- Um banco de dados foi enviado com seu aplicativo, mas deve ser copiado ou movido para o armazenamento gravável no dispositivo móvel.
- Você precisa fazer um backup ou cópia do banco de dados.
- Você precisa ver, mover ou renomear o arquivo de banco de dados.
Em geral, mover, renomear ou copiar um arquivo de banco de dados é o mesmo processo que qualquer outro tipo de arquivo com algumas considerações adicionais:
- Todos os conexões de banco de dados devem ser fechados antes de tentar mover o arquivo de banco de dados.
- Se você usar o log Write-Ahead, o SQLite criará um arquivo de Acesso à Memória Compartilhada (.shm) e um arquivo (Log Write Ahead) (.wal). Verifique se você também aplica alterações a esses arquivos.
Para obter mais informações, consulte Tratamento de arquivos em Xamarin.Forms.