Xamarin.Forms Bases de données locales

Télécharger l’exemple Télécharger l’exemple

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 les é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 à des 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édez 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 de 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 les valeurs suivantes :

  • 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 lorsque l’appareil est verrouillé.
  • ProtectionCompleteUnlessOpen: le fichier est chiffré jusqu’à ce qu’il soit ouvert, mais est ensuite 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 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 extrait la couche d’accès aux données 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’extension des opérations de données à mesure que l’application grandit. L’application Todo définit une TodoItemDatabase classe à cet effet.

Initialisation tardive

Utilise l’initialisation TodoItemDatabase 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 en tant que 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 au champ simultanément, ils utilisent tous 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 se poursuivent immédiatement, car la valeur est disponible.

Notes

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 pendant 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 de connexions plusieurs fois au cours d’une seule session d’application.

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 paresseuse 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 Lazy<T> types et Task<T> pour créer une tâche initialisée paresseux 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é de fabrique se termine, la valeur initialisée paresseux est disponible et toutes les méthodes qui attendent le AsyncLazy<T> instance reçoivent la valeur. 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 simple carte relationnelle d’objet (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, par 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 celles décrites dans cet article et dans l’exemple d’application. Les sections suivantes couvrent les fonctionnalités importantes pour la scalabilité.

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

Journalisation à l’avance en écriture

Par défaut, SQLite utilise un journal de restauration traditionnel. Une copie du contenu de la 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. Le COMMIT se produit lorsque le journal de restauration est supprimé.

Write-Ahead Journalisation (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 point de contrôle.

Wal peut être plus rapide pour les bases de données locales, car les lecteurs et les rédacteurs 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 la page, ajoute des associations de fichiers supplémentaires à la base de données et ajoute l’opération de point de contrôle supplémentaire.

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

await Database.EnableWriteAheadLoggingAsync();

Pour plus d’informations, consultez Journalisation Write-Ahead 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 pouvant être écrit sur l’appareil mobile.
  • Vous devez effectuer une sauvegarde ou une copie de la base de données.
  • Vous devez modifier, 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 aux bases de données doivent être fermées avant d’essayer de déplacer le fichier de base de données.
  • Si vous utilisez la journalisation en pré-écriture, SQLite crée un fichier d’accès à la mémoire partagée (.shm) et un fichier (Journal d’écriture anticipée) (.wal). Veillez également à appliquer des modifications à ces fichiers.

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