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 dépôt 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 threading sérialisé.
  • NoMutex: la connexion est ouverte en mode multithreading.
  • PrivateCache: la connexion ne participe pas au cache partagé, même si elle est activée.
  • ReadWrite: la connexion peut lire et écrire des données.
  • SharedCache: la connexion participe au 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’à ce qu’il soit ouvert, 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 votre base de données sera utilisée. Pour plus d’informations sur SQLiteOpenFlags, consultez Ouverture d’une nouvelle base de données Connecter ion sur sqlite.org.

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

Une classe wrapper de base de données extrait 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 base de données, ce qui facilite la refactorisation ou l’expansion des opérations de données à mesure que l’application augmente. L’application Todo définit une TodoItemDatabase classe à cet effet.

Initialisation différée

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 TodoItemDatabase classe 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 relationnelle d’objet simple (ORM) 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 plus de fonctionnalités que ce qui est abordé dans cet article et l’exemple d’application. Les sections suivantes couvrent les fonctionnalités importantes pour l’extensibilité.

Pour plus d’informations, consultez la documentation SQLite sur sqlite.org.

Journalisation en écriture anticipée

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, puis les modifications sont écrites directement dans le fichier de base de données. La validation se produit lorsque le journal de restauration est supprimé.

La journalisation en écriture anticipée (WAL) é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 case activée point.

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 de lecture et d’é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 de case activée point supplémentaire.

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

await Database.EnableWriteAheadLoggingAsync();

Pour plus d’informations, consultez la journalisation sqLite write-ahead 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 mettre en 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 :

  • Toutes les connexions de base de données doivent être fermées avant de tenter de déplacer le fichier de base de données.
  • Si vous utilisez la journalisation write-ahead, 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.