Compartilhar via


Bancos de dados locais do .NET MAUI

Procurar amostra. Procurar no exemplo

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:

  1. Instale o pacote NuGet.
  2. Configure as constantes.
  3. Crie uma classe de acesso ao banco de dados.
  4. Acesse os dados.
  5. 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.