Bancos de dados locais do .NET MAUI
O mecanismo de banco de dados SQLite permite que os aplicativos .NET Multi-Platform App UI (.NET MAUI) carreguem e salvem objetos de dados no código compartilhado. Você pode integrar o SQLite.NET em aplicativos .NET MAUI para armazenar e recuperar informações em um banco de dados local seguindo estas etapas:
- Instale o pacote NuGet.
- Configure as constantes.
- Crie uma classe de acesso ao banco de dados.
- Acesse os dados.
- Configuração avançada.
Este artigo usa o pacote NuGet sqlite-net-pcl para fornecer acesso ao banco de dados SQLite a uma tabela para armazenar itens de tarefas. Uma alternativa é usar o pacote NuGet Microsoft.Data.Sqlite, que é um provedor ADO.NET leve para SQLite. O Microsoft.Data.Sqlite implementa as abstrações comuns ADO.NET para funcionalidades como conexões, comandos e leitores de dados.
Instalar o pacote NuGet do SQLite
Use o gerenciador de pacotes NuGet para pesquisar o pacote sqlite-net-pcl e adicionar a versão mais recente ao seu projeto de aplicativo .NET MAUI.
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 em projetos .NET MAUI.
Importante
O SQLite.NET é uma biblioteca de terceiros com suporte do repositório praeclarum/sqlite-net.
Instalar o SQLitePCLRaw.bundle_green
Além do sqlite-net-pcl, você precisa instalar temporariamente a dependência subjacente que expõe o SQLite em cada plataforma:
- ID: SQLitePCLRaw.bundle_green
- Versão:>= 2.1.0
- Autores: Eric Sink
- Proprietários: Eric Sink
- Link do NuGet: SQLitePCLRaw.bundle_green
Configurar as constantes do aplicativo
Dados de configuração, como o nome e o caminho do arquivo do banco de dados, podem ser armazenados como constantes em seu aplicativo. O projeto de exemplo inclui um Constants.cs que fornece os 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 =>
Path.Combine(FileSystem.AppDataDirectory, DatabaseFilename);
}
Neste exemplo, o arquivo de constantes especifica os valores de enumeração SQLiteOpenFlag
padrão que são usados para inicializar a conexão de banco de dados. A enumeração SQLiteOpenFlag
dá suporte aos seguintes 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 de multithreading.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 habilitada.ProtectionComplete
: O arquivo é criptografado e inacessível enquanto o dispositivo estiver 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 Opening A New Database Connection no sqlite.org.
Criar uma classe de acesso ao banco de dados
Uma classe de 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 de exemplo define uma classe TodoItemDatabase
para essa finalidade.
Inicialização lenta
O TodoItemDatabase
usa a inicialização lenta assíncrona para adiar a inicialização do banco de dados até que ele seja acessado pela primeira vez, com um método simples Init
que é chamado por cada método na classe:
public class TodoItemDatabase
{
SQLiteAsyncConnection Database;
public TodoItemDatabase()
{
}
async Task Init()
{
if (Database is not null)
return;
Database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
var result = await Database.CreateTableAsync<TodoItem>();
}
...
}
Métodos de manipulação de dados
A classe TodoItemDatabase
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.
O exemplo a seguir mostra os métodos de manipulação de dados no aplicativo de exemplo:
public class TodoItemDatabase
{
...
public async Task<List<TodoItem>> GetItemsAsync()
{
await Init();
return await Database.Table<TodoItem>().ToListAsync();
}
public async Task<List<TodoItem>> GetItemsNotDoneAsync()
{
await Init();
return await Database.Table<TodoItem>().Where(t => t.Done).ToListAsync();
// SQL queries are also possible
//return await Database.QueryAsync<TodoItem>("SELECT * FROM [TodoItem] WHERE [Done] = 0");
}
public async Task<TodoItem> GetItemAsync(int id)
{
await Init();
return await Database.Table<TodoItem>().Where(i => i.ID == id).FirstOrDefaultAsync();
}
public async Task<int> SaveItemAsync(TodoItem item)
{
await Init();
if (item.ID != 0)
return await Database.UpdateAsync(item);
else
return await Database.InsertAsync(item);
}
public async Task<int> DeleteItemAsync(TodoItem item)
{
await Init();
return await Database.DeleteAsync(item);
}
}
Acessar dados
A classe TodoItemDatabase
pode ser registrada como um singleton que pode ser usado em todo o aplicativo se você estiver usando a injeção de dependência. Por exemplo, você pode registrar suas páginas e a classe de acesso ao banco de dados como serviços no objeto IServiceCollection, em MauiProgram.cs, com os métodos AddSingleton
e AddTransient
:
builder.Services.AddSingleton<TodoListPage>();
builder.Services.AddTransient<TodoItemPage>();
builder.Services.AddSingleton<TodoItemDatabase>();
Esses serviços podem ser injetados automaticamente nos construtores de classe e acessados:
TodoItemDatabase database;
public TodoItemPage(TodoItemDatabase todoItemDatabase)
{
InitializeComponent();
database = todoItemDatabase;
}
async void OnSaveClicked(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(Item.Name))
{
await DisplayAlert("Name Required", "Please enter a name for the todo item.", "OK");
return;
}
await database.SaveItemAsync(Item);
await Shell.Current.GoToAsync("..");
}
Como alternativa, podem ser criadas novas instâncias da classe de acesso ao banco de dados:
TodoItemDatabase database;
public TodoItemPage()
{
InitializeComponent();
database = new TodoItemDatabase();
}
Para obter mais informações sobre a injeção de dependência em aplicativos .NET MAUI, confira Injeção de dependência.
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 abrangem recursos importantes para escalabilidade.
Para obter mais informações, consulte a SQLite Documentation no sqlite.org.
Registro em logs write-ahead
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.
O WAL (registro write-ahead) grava as alterações primeiro em um arquivo WAL separado. 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 checkpoint.
O WAL pode ser mais rápido para bancos de dados locais porque leitores e gravadores não se bloqueiam mutuamente, 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 inclui a operação extra de checkpointing.
Para habilitar o WAL no SQLite.NET, chame o método EnableWriteAheadLoggingAsync
na instância SQLiteAsyncConnection
:
await Database.EnableWriteAheadLoggingAsync();
Para obter mais informações, consulte SQLite Write-Ahead Logging no 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 versionar, 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:
- Todas as conexões de banco de dados devem ser fechadas antes de tentar mover o arquivo de banco de dados.
- Se você usar o registro write-ahead, o SQLite criará um arquivo de Acesso à Memória Compartilhada (.shm) e um arquivo de registro write ahead (.wal). Verifique se você aplicou quaisquer alterações a esses arquivos também.