Compartilhar via


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 todo. Este artigo descreve como usar SQLite.Net em código compartilhado para armazenar e recuperar informações em um banco de dados local.

Capturas de tela do aplicativo Todolist no iOS e Android

Integre SQLite.NET em aplicativos móveis seguindo estas etapas:

  1. Instale o pacote NuGet.
  2. Configurar constantes.
  3. Crie uma classe de acesso ao banco de dados.
  4. Acesse os dados no Xamarin.Forms.
  5. Configuração avançada.

Instalar o pacote NuGet do SQLite

Use o gerenciador de pacotes NuGet para procurar 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 que é suportada pelo repositório praeclarum/sqlite-net.

Configurar constantes de aplicativo

O projeto de exemplo inclui um arquivo Constants.cs que fornece dados de configuração comuns:

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 os valores de enum padrão SQLiteOpenFlag que são usados para inicializar a conexão de banco de dados. O SQLiteOpenFlag enum suporta estes 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 habilitada.
  • ProtectionComplete: O arquivo é criptografado e inacessível enquanto o dispositivo está bloqueado.
  • ProtectionCompleteUnlessOpen: O arquivo é criptografado até ser aberto, mas fica acessível 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 o banco de dados será usado. Para obter mais informações sobre SQLiteOpenFlagso , 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 inicialização assíncrona lenta, 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 retorna 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. Então, quando a construção é 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, uma vez que 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. Usar 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 assíncrona com preguiça

Para iniciar a inicialização do banco de dados, evitar bloquear a execução e ter a oportunidade de capturar exceções, o aplicativo de exemplo usa a iniciação assíncrona lenta, 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 com preguiça 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 de 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 com preguiça está disponível e todos os métodos que aguardam a AsyncLazy<T> instância recebem o valor. Para obter mais informações, confira 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 mapa relacional de objeto (ORM) simples que permite armazenar e recuperar objetos sem escrever 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 campo, por meio do Instance qual as operações de acesso a TodoItemDatabase dados na 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.

O Write-Ahead Logging (WAL) grava as alterações em um arquivo WAL separado primeiro. No modo WAL, um COMMIT é um registro especial, anexado ao arquivo WAL, que permite que várias transações ocorram em um único arquivo WAL. Um arquivo WAL é mesclado novamente no arquivo de banco de dados em uma operação especial chamada ponto de verificação.

A WAL pode ser mais rápida 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 arquivo adicionais ao banco de dados e adiciona a operação de checkpoint extra.

Para habilitar a WAL no SQLite.NET, chame o EnableWriteAheadLoggingAsyncSQLiteAsyncConnection método na instância:

await Database.EnableWriteAheadLoggingAsync();

Para obter mais informações, consulte SQLite Write-Ahead Logging 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 fornecido 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 versão, 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 Write-Ahead Logging, o SQLite criará um arquivo de Acesso à Memória Compartilhada (.shm) e um arquivo (Log de Gravação Antecipada) (.wal). Certifique-se de aplicar quaisquer alterações a esses arquivos também.

Para obter mais informações, consulte Tratamento de arquivos no Xamarin.Forms.