Compartir a través de


Bases de datos locales de .NET MAUI

Examinar ejemplo. Examinar el ejemplo

El motor de base de datos de SQLite permite que las aplicaciones .NET Multi-platform App UI (.NET MAUI) carguen y guarden objetos de datos en código compartido. Puedes integrar SQLite.NET en aplicaciones .NET MAUI para almacenar y recuperar información en una base de datos local siguiendo estos pasos:

  1. Instala el paquete NuGet .
  2. Configurar constantes.
  3. Crear una clase de acceso a la base de datos.
  4. Acceder a los datos.
  5. Configuración avanzada.

Este artículo usa el paquete NuGet sqlite-net-pcl para proporcionar acceso a la base de datos de SQLite a una tabla para almacenar elementos de tareas pendientes. Una alternativa es usar el paquete NuGet Microsoft.Data.Sqlite, que es un proveedor ligero de ADO.NET para SQLite. Microsoft.Data.Sqlite implementa las abstracciones comunes de ADO.NET para funciones como conexiones, comandos y lectores de datos.

Instalar el paquete de SQLite NuGet

Usa el administrador de paquetes NuGet para buscar el paquete sqlite-net-pcl y agrega la versión más reciente a tu proyecto de aplicación .NET MAUI.

Hay varios paquetes NuGet con nombres similares. El paquete correcto tiene estos atributos:

  • Id.: sqlite-net-pcl
  • Autores: SQLite-net
  • Propietarios: praeclarum
  • Vínculo de NuGet: sqlite-net-pcl

Independientemente del nombre del paquete, usa el paquete de NuGet sqlite-net-pcl incluso en los proyectos de .NET MAUI.

Importante

SQLite.NET es una biblioteca de terceros compatible con el repositorio praeclarum/sqlite-net.

Instalar SQLitePCLRaw.bundle_green

Además de de sqlite-net-pcl, necesitas instalar temporalmente la dependencia subyacente que expone SQLite en cada plataforma:

  • ID: SQLitePCLRaw.bundle_green
  • Versión:>= 2.1.0
  • Autores: Eric Sink
  • Propietarios: Eric Sink
  • Vínculo NuGet: SQLitePCLRaw.bundle_green

Configurar constantes de aplicación

Los datos de configuración, como el nombre de archivo y la ruta de acceso de la base de datos, se pueden almacenar como constantes en tu aplicación. El proyecto de ejemplo incluye un archivo Constants.cs que proporciona datos de configuración comunes:

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);
}

En este ejemplo, el archivo de constantes especifica valores de enumeración SQLiteOpenFlag predeterminados que se usan para inicializar la conexión de base de datos. La enumeración SQLiteOpenFlag admite estos valores:

  • Create: la conexión creará automáticamente el archivo de base de datos si no existe.
  • FullMutex: la conexión se abre en modo de subproceso serializado.
  • NoMutex: la conexión se abre en modo multiproceso.
  • PrivateCache: la conexión no participará en la caché compartida, incluso si está habilitada.
  • ReadWrite: la conexión puede leer y escribir datos.
  • SharedCache: la conexión participará en la caché compartida, si está habilitada.
  • ProtectionComplete: el archivo está cifrado y es inaccesible mientras el dispositivo está bloqueado.
  • ProtectionCompleteUnlessOpen: el archivo está cifrado hasta que se abre, pero después es accesible incluso si el usuario bloquea el dispositivo.
  • ProtectionCompleteUntilFirstUserAuthentication: el archivo está cifrado hasta después de que el usuario haya arrancado y desbloqueado el dispositivo.
  • ProtectionNone: el archivo de base de datos no está cifrado.

Es posible que tengas que especificar marcas diferentes en función de cómo se usará la base de datos. Para obtener más información sobre SQLiteOpenFlags, consulta Abrir una nueva conexión de base de datos en sqlite.org.

Crear una clase de acceso de base de datos

Una clase contenedora de base de datos abstrae la capa de acceso a datos del resto de la aplicación. Esta clase centraliza la lógica de consulta y simplifica la gestión de la inicialización de la base de datos, lo que facilita la refactorización o expansión de las operaciones de datos a medida que crece la aplicación. La aplicación de ejemplo define una clase TodoItemDatabase para este propósito.

Inicialización diferida

TodoItemDatabase usa la inicialización diferida asincrónica para retrasar la inicialización de la base de datos hasta que se accede por primera vez, con un método simple Init al que llama cada método de la clase:

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 manipulación de datos

La clase TodoItemDatabase incluye métodos para los cuatro tipos de manipulación de datos: crear, leer, editar y eliminar. La biblioteca de SQLite.NET proporciona un mapa relacional de objetos (ORM) simple que te permite almacenar y recuperar objetos sin escribir instrucciones SQL.

En el ejemplo siguiente se muestran los métodos de manipulación de datos en la aplicación de ejemplo:

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);
    }
}

Acceso a los datos

La clase TodoItemDatabase se puede registrar como singleton que se puede usar en toda la aplicación si usas la inserción de dependencias. Por ejemplo, puedes registrar las páginas y la clase de acceso a la base de datos como servicios en el objeto IServiceCollection, en MauiProgram.cs, con los métodos AddSingleton y AddTransient:

builder.Services.AddSingleton<TodoListPage>();
builder.Services.AddTransient<TodoItemPage>();

builder.Services.AddSingleton<TodoItemDatabase>();

Estos servicios se pueden insertar automáticamente en constructores de clase y acceder a ellos:

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, se pueden crear nuevas instancias de la clase de acceso a la base de datos:

TodoItemDatabase database;

public TodoItemPage()
{
    InitializeComponent();
    database = new TodoItemDatabase();
}

Para obtener más información sobre la inserción de dependencias en aplicaciones MAUI de .NET, vea Inserción de dependencias.

Configuración avanzada

SQLite proporciona una API sólida con más características de las que se tratan en este artículo y en la aplicación de ejemplo. En las secciones siguientes se tratan las características que son importantes para la escalabilidad.

Para obtener más información, consulta SQLite Documentation en sqlite.org.

Registro de escritura previa

De forma predeterminada, SQLite usa un diario de reversión tradicional. Una copia del contenido de la base de datos sin cambios se escribe en un archivo de reversión independiente y después los cambios se escriben directamente en el archivo de base de datos. COMMIT se produce cuando se elimina el diario de reversión.

El registro de escritura previa (WAL) escribe primero los cambios en un archivo WAL independiente. En el modo WAL, COMMIT es un registro especial, anexado al archivo WAL, que permite que varias transacciones se produzcan en un único archivo WAL. Un archivo WAL se combina de nuevo en el archivo de base de datos en una operación especial denominada punto de control.

WAL puede ser más rápido para las bases de datos locales porque los lectores y escritores no se bloquean entre sí, lo que permite que las operaciones de lectura y escritura sean simultáneas. Pero el modo WAL no permite cambios en el tamaño de página, agrega asociaciones de archivos adicionales a la base de datos y agrega la operación de punto de control adicional.

Para habilitar WAL en SQLite.NET, llama al método EnableWriteAheadLoggingAsync en la instancia SQLiteAsyncConnection:

await Database.EnableWriteAheadLoggingAsync();

Para obtener más información, consulta SQLite Write-Ahead Logging en sqlite.org.

Copia de una base de datos

Hay varios casos en los que puede ser necesario copiar una base de datos de SQLite:

  • Una base de datos se ha enviado con la aplicación, pero debe copiarse o moverse al almacenamiento que se puede escribir en el dispositivo móvil.
  • Debes realizar una copia de seguridad o una copia de la base de datos.
  • Debes actualizar, mover o cambiar el nombre del archivo de base de datos.

En general, mover, cambiar el nombre o copiar un archivo de base de datos es el mismo proceso que cualquier otro tipo de archivo con algunas consideraciones adicionales:

  • Todos las conexiones de base de datos deben cerrarse antes de intentar mover el archivo de base de datos.
  • Si usas el Registro de escritura previa, SQLite creará un archivo de acceso a memoria compartida (.shm) y un archivo .wal (Write Ahead Log). Asegúrate de aplicar también los cambios a estos archivos.