Xamarin.Forms Bases de datos locales

Ejemplo de descarga Descarga del ejemplo

El motor de base de datos sqlite permite a Xamarin.Forms las aplicaciones cargar y guardar objetos de datos en código compartido. La aplicación de ejemplo usa una tabla de base de datos sqlite para almacenar elementos de tareas pendientes. En este artículo se describe cómo usar SQLite.Net código compartido para almacenar y recuperar información en una base de datos local.

aplicación Todolist en iOS y Android de la aplicaciónTodolist en iOS y Android

Integre SQLite.NET aplicaciones móviles siguiendo estos pasos:

  1. Instale el NuGet .
  2. Configure constantes.
  3. Cree una clase de acceso a la base de datos.
  4. Access data in Xamarin.Forms.
  5. Configuración avanzada.

Instalación del paquete de NuGet SQLite

Use el NuGet paquetes para buscar sqlite-net-pcl y agregar la versión más reciente al proyecto de código compartido.

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

Nota:

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

Configuración de constantes de 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
    {
        get
        {
            var basePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            return Path.Combine(basePath, DatabaseFilename);
        }
    }
}

El archivo de constantes especifica los valores SQLiteOpenFlag de enumeración predeterminados que se usan para inicializar la conexión de base de datos. La SQLiteOpenFlag enumeración 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 e inaccesible mientras el dispositivo está bloqueado.
  • ProtectionCompleteUnlessOpen: el archivo se cifra hasta que se abre, pero luego es accesible incluso si el usuario bloquea el dispositivo.
  • ProtectionCompleteUntilFirstUserAuthentication: el archivo se cifra 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 tenga que especificar marcas diferentes en función de cómo se usará la base de datos. Para obtener más información sobre SQLiteOpenFlags , vea Abrir una nueva conexión de base de SQLiteOpenFlags en sqlite.org.

Creación de una clase de acceso a la 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 administració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 Todo define una TodoItemDatabase clase para este propósito.

Inicialización diferida

usa TodoItemDatabase la inicialización diferida asincrónica, representada por la clase personalizada, para retrasar la inicialización de la base de datos hasta que se accede AsyncLazy<T> por primera 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);
    }

    //...
}

El campo se usa para crear la tabla de base de datos para el objeto , si aún no existe, y devuelve como InstanceTodoItem un TodoItemDatabase singleton. El Instance campo de tipo se construye la primera vez que se AsyncLazy<TodoItemDatabase> espera. Si varios subprocesos intentan acceder al campo simultáneamente, todos usarán la construcción única. Después, cuando se complete la construcción, se completan await todas las operaciones. Además, las operaciones await después de completar la construcción continúan inmediatamente, ya que el valor está disponible.

Nota:

La conexión de base de datos es un campo estático que garantiza que se usa una conexión de base de datos única durante la vida útil de la aplicación. El uso de una conexión estática persistente ofrece un mejor rendimiento que abrir y cerrar conexiones varias veces durante una sola sesión de aplicación.

Inicialización diferida asincrónica

Para iniciar la inicialización de la base de datos, evitar el bloqueo de la ejecución y tener la oportunidad de detectar excepciones, la aplicación de ejemplo usa la inicialización diferida asincrónica, representada por la AsyncLazy<T> clase :

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

La clase combina los tipos y para crear una tarea inicializada de forma diferida AsyncLazyLazy<T> que representa la Task<T> inicialización de un recurso. El delegado de fábrica que se pasa al constructor puede ser sincrónico o asincrónico. Los delegados de generador se ejecutarán en un subproceso del grupo de subprocesos y no se ejecutarán más de una vez (incluso cuando varios subprocesos intenten iniciarlos simultáneamente). Cuando se completa un delegado de generador, el valor inicializado diferido está disponible y los métodos que esperan a la instancia AsyncLazy<T> reciben el valor. Para más información, vea AsyncLazy.

Métodos de manipulación de datos

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

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

Acceso a datos en Xamarin.Forms

La clase expone el campo , a través del cual se pueden invocar las operaciones de acceso a datos TodoItemDatabaseInstance de la clase TodoItemDatabase :

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

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 cubren las características que son importantes para la escalabilidad.

Para obtener más información, consulte la documentación de SQLite sqlite.org.

Registro de escritura por adelantado

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

Write-Ahead Logging (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 se produzcan varias transacciones 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. Sin embargo, el modo WAL no permite cambios en el tamaño de página,agrega asociaciones de archivo adicionales a la base de datos y agrega la operación de punto de comprobación adicional.

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

await Database.EnableWriteAheadLoggingAsync();

Para obtener más información, vea SQLite Write-Ahead Logging on 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.
  • Debe realizar una copia de seguridad o una copia de la base de datos.
  • Debe cambiar la versión, 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:

  • Todas las conexiones de base de datos deben cerrarse antes de intentar mover el archivo de base de datos.
  • Si usa el registro deescritura por adelantado, SQLite creará un archivo de acceso a memoria compartida (.shm) y un archivo (Registro de escritura con antelación) (.wal). Asegúrese de aplicar también los cambios en estos archivos.

Para obtener más información, vea File Handling in Xamarin.Forms.