Xamarin.Forms Bases de données locales

Le moteur de base de données SQLite permet Xamarin.Forms aux applications de charger et d’enregistrer des objets de données dans du code partagé. L’exemple d’application utilise une table de base de données SQLite pour stocker des éléments todo. Cet article explique comment utiliser SQLite.Net dans le code partagé pour stocker et récupérer des informations dans une base de données locale.

Captures d’écran de l’application Todolist sur iOS et Android

Intégrez SQLite.NET aux applications mobiles en procédant comme suit :

  1. Installez le package NuGet.
  2. Configurez des constantes.
  3. Créez une classe d’accès à la base de données.
  4. Accéder aux données dans Xamarin.Forms.
  5. Configuration avancée.

Installer le package NuGet SQLite

Utilisez le gestionnaire de package NuGet pour rechercher sqlite-net-pcl et ajouter la dernière version au projet de code partagé.

Il existe plusieurs packages NuGet portant des noms similaires. Le package correct possède ces attributs :

  • ID : sqlite-net-pcl
  • Auteurs : SQLite-net
  • Propriétaires : praeclarum
  • Lien NuGet : sqlite-net-pcl

Ne vous fiez pas au nom du package. Vous devez utiliser le package NuGet sqlite-net-pcl, même dans les projets .NET Standard.

Important

SQLite.NET est une bibliothèque tierce prise en charge à partir du référentiel praeclarum/sqlite-net.

Configurer des constantes d’application

L’exemple de projet inclut un fichier Constants.cs qui fournit des données de configuration courantes :

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

Le fichier constantes spécifie les valeurs d’énumération par défaut SQLiteOpenFlag utilisées pour initialiser la connexion de base de données. L’énumération SQLiteOpenFlag prend en charge ces valeurs :

  • Create : La connexion crée automatiquement le fichier de base de données s’il n’existe pas.
  • FullMutex : La connexion est ouverte en mode de thread sérialisé.
  • NoMutex : La connexion est ouverte en mode multithreading.
  • PrivateCache : La connexion ne participe pas dans le cache partagé, même si elle est activée.
  • ReadWrite : La connexion peut lire et écrire des données.
  • SharedCache : La connexion participe dans le cache partagé si elle est activée.
  • ProtectionComplete : Le fichier est chiffré et inaccessible pendant que l’appareil est verrouillé.
  • ProtectionCompleteUnlessOpen : Le fichier est chiffré jusqu’à son ouverture, mais il est alors accessible même si l’utilisateur verrouille l’appareil.
  • ProtectionCompleteUntilFirstUserAuthentication : Le fichier est chiffré jusqu’à ce que l’utilisateur ait démarré et déverrouillé l’appareil.
  • ProtectionNone : Le fichier de base de données n’est pas chiffré.

Vous devrez peut-être spécifier différents indicateurs en fonction de la façon dont vous utiliserez votre base de données. Pour obtenir plus d’informations sur SQLiteOpenFlags, consultez Ouverture d’une nouvelle connexion de base de données sur sqlite.org.

Créer une classe d’accès à la base de données

Une classe wrapper de base de données abstrait la couche d’accès aux données à partir du reste de l’application. Cette classe centralise la logique de requête et simplifie la gestion de l’initialisation de la base de données, ce qui facilite la refactorisation ou l’expansion des opérations de données au fur et à mesure du développement de l’application. L’application Todo définit une TodoItemDatabase classe à cet effet.

Initialisation tardive

Il TodoItemDatabase utilise l’initialisation différée asynchrone, représentée par la classe personnalisée AsyncLazy<T> , pour retarder l’initialisation de la base de données jusqu’à ce qu’elle soit utilisée pour la première fois :

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

    //...
}

Le Instance champ est utilisé pour créer la table de base de données de l’objet TodoItem , s’il n’existe pas déjà, et retourne un TodoItemDatabase singleton. Le Instance champ, de type AsyncLazy<TodoItemDatabase> , est construit la première fois qu’il est attendu. Si plusieurs threads tentent d’accéder simultanément au champ, ils utilisent toutes la construction unique. Ensuite, une fois la construction terminée, toutes les await opérations se terminent. En outre, toutes les await opérations une fois la construction terminée continuent immédiatement depuis la disponibilité de la valeur.

Remarque

La connexion de base de données est un champ statique qui garantit qu’une seule connexion de base de données est utilisée pour la durée de vie de l’application. L’utilisation d’une connexion statique persistante offre de meilleures performances que l’ouverture et la fermeture des connexions plusieurs fois pendant une session d’application unique.

Initialisation différée asynchrone

Pour démarrer l’initialisation de la base de données, éviter de bloquer l’exécution et avoir la possibilité d’intercepter des exceptions, l’exemple d’application utilise l’initalisation différée asynchrone, représentée par la 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();
    }
}

La AsyncLazy classe combine les types et Task<T> les Lazy<T> types pour créer une tâche initialisée différée qui représente l’initialisation d’une ressource. Le délégué de fabrique passé au constructeur peut être synchrone ou asynchrone. Les délégués de fabrique s’exécutent sur un thread de pool de threads et ne sont pas exécutés plusieurs fois (même lorsque plusieurs threads tentent de les démarrer simultanément). Lorsqu’un délégué d’usine est terminé, la valeur différée initialisée est disponible et toutes les méthodes en attente de réception de la valeur par l’instance AsyncLazy<T> . Pour plus d'informations, consultez AsyncLazy.

Méthodes de manipulation des données

La classe TodoItemDatabase inclut des méthodes pour les quatre types de manipulation de données : créer, lire, modifier et supprimer. La bibliothèque SQLite.NET fournit une carte objet-relationnel (ORM) simple qui vous permet de stocker et de récupérer des objets sans écrire d’instructions 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);
    }
}

Accéder aux données dans Xamarin.Forms

La TodoItemDatabase classe expose le Instance champ, via lequel les opérations d’accès aux données de la TodoItemDatabase classe peuvent être appelées :

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

Configuration avancée

SQLite fournit une API robuste avec davantage de fonctionnalités que ce qui est traité dans cet article et l’exemple d’application. Les sections suivantes abordent les fonctionnalités importantes pour l’extensibilité.

Pour obtenir plus d’informations, voir Documentation SQLite sur sqlite.org.

Journalisation WAL (Write-Ahead Logging)

Par défaut, SQLite utilise un journal de restauration traditionnel. Une copie du contenu de base de données inchangé est écrite dans un fichier de restauration distinct. Les modifications sont ensuite écrites directement dans le fichier de base de données. Le COMMIT se produit lorsque le journal de restauration est supprimé.

La journalisation WAL (Write-Ahead Logging) écrit d’abord les modifications dans un fichier WAL distinct. En mode WAL, un COMMIT est un enregistrement spécial, ajouté au fichier WAL, qui permet à plusieurs transactions de se produire dans un seul fichier WAL. Un fichier WAL est fusionné dans le fichier de base de données dans une opération spéciale appelée point de contrôle.

WAL peut être plus rapide pour les bases de données locales, car les lecteurs et les enregistreurs ne se bloquent pas mutuellement, ce qui permet aux opérations en lecture et en écriture d’être simultanées. Toutefois, le mode WAL n’autorise pas les modifications apportées à la taille de page, ajoute des associations de fichiers supplémentaires à la base de données et ajoute l’opération supplémentaire de points de contrôle.

Pour activer WAL dans SQLite.NET, appelez la méthode EnableWriteAheadLoggingAsync sur l’instance SQLiteAsyncConnection :

await Database.EnableWriteAheadLoggingAsync();

Pour obtenir plus d’informations, consultez Write-Ahead Logging de SQLite sur sqlite.org.

Copier une base de données

Il existe plusieurs cas où il peut être nécessaire de copier une base de données SQLite :

  • Une base de données a été fournie avec votre application, mais doit être copiée ou déplacée vers un stockage accessible en écriture sur l’appareil mobile.
  • Vous devez effectuer une sauvegarde ou une copie de la base de données.
  • Vous devez attribuer une version, déplacer ou renommer le fichier de base de données.

En général, le déplacement, le changement de nom ou la copie d’un fichier de base de données est le même processus que tout autre type de fichier avec quelques considérations supplémentaires :

  • Vous devez fermer toutes les connexions de base de données avant de tenter de déplacer le fichier de base de données.
  • Si vous utilisez la journalisation Write-Ahead Logging, SQLite crée un fichier d’accès à la mémoire partagée (.shm) et un fichier (Write Ahead Log) (.wal). Veillez également à appliquer les modifications apportées à ces fichiers.

Pour plus d’informations, consultez Gestion des fichiers dans Xamarin.Forms.