Xamarin.Forms Lokale Datenbanken

Beispiel herunterladen Das Beispiel herunterladen

Mit der SQLite-Datenbank-Engine können Xamarin.Forms Anwendungen Datenobjekte in freigegebenem Code laden und speichern. Die Beispielanwendung verwendet eine SQLite-Datenbanktabelle zum Speichern von Todo-Elementen. In diesem Artikel wird beschrieben, wie Sie SQLite.Net in freigegebenem Code verwenden, um Informationen in einer lokalen Datenbank zu speichern und abzurufen.

Screenshots der Todolist-App unter iOS und Android

Integrieren Sie SQLite.NET in mobile Apps, indem Sie die folgenden Schritte ausführen:

  1. Installieren Sie das NuGet-Paket.
  2. Konfigurieren Sie Konstanten.
  3. Erstellen Sie eine Datenbankzugriffsklasse.
  4. Zugreifen auf Daten in Xamarin.Forms.
  5. Erweiterte Konfiguration.

Installieren des SQLite NuGet-Pakets

Verwenden Sie den NuGet-Paket-Manager, um nach sqlite-net-pcl zu suchen und dem Freigegebenen Codeprojekt die neueste Version hinzuzufügen.

Es gibt eine Reihe von NuGet-Paketen mit ähnlichen Namen. Das richtige Paket verfügt über die folgenden Attribute:

  • ID: sqlite-net-pcl
  • Ersteller: SQLite-net
  • Besitzer: praeclarum
  • NuGet-Link:sqlite-net-pcl

Verwenden Sie trotz des Paketnamens in .NET Standard-Projekten das NuGet-Paket sqlite-net-pcl.

Wichtig

SQLite.NET ist eine Drittanbieterbibliothek, die vom praeclarum/sqlite-net-Repository unterstützt wird.

Konfigurieren von App-Konstanten

Das Beispielprojekt enthält eine Datei Constants.cs , die allgemeine Konfigurationsdaten bereitstellt:

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

Die Konstantendatei gibt die Standard-Enumerationswerte SQLiteOpenFlag an, die zum Initialisieren der Datenbankverbindung verwendet werden. Die SQLiteOpenFlag Enumeration unterstützt die folgenden Werte:

  • Create: Die Verbindung erstellt die Datenbankdatei automatisch, wenn sie nicht vorhanden ist.
  • FullMutex: Die Verbindung wird im serialisierten Threadingmodus geöffnet.
  • NoMutex: Die Verbindung wird im Multithreadingmodus geöffnet.
  • PrivateCache: Die Verbindung nimmt nicht am freigegebenen Cache teil, auch wenn er aktiviert ist.
  • ReadWrite: Die Verbindung kann Daten lesen und schreiben.
  • SharedCache: Die Verbindung nimmt am freigegebenen Cache teil, wenn er aktiviert ist.
  • ProtectionComplete: Die Datei ist verschlüsselt und nicht zugänglich, während das Gerät gesperrt ist.
  • ProtectionCompleteUnlessOpen: Die Datei wird verschlüsselt, bis sie geöffnet wird, ist aber auch dann zugänglich, wenn der Benutzer das Gerät sperrt.
  • ProtectionCompleteUntilFirstUserAuthentication: Die Datei wird verschlüsselt, bis der Benutzer das Gerät gestartet und entsperrt hat.
  • ProtectionNone: Die Datenbankdatei ist nicht verschlüsselt.

Je nachdem, wie Ihre Datenbank verwendet wird, müssen Sie möglicherweise unterschiedliche Flags angeben. Weitere Informationen zu SQLiteOpenFlagsfinden Sie unter Öffnen einer neuen Datenbankverbindung auf sqlite.org.

Erstellen einer Datenbankzugriffsklasse

Eine Datenbank-Wrapperklasse abstrahiert die Datenzugriffsebene vom Rest der App. Diese Klasse zentralisiert die Abfragelogik und vereinfacht die Verwaltung der Datenbankinitialisierung, was das Umgestalten oder Erweitern von Datenvorgängen erleichtert, wenn die App wächst. Die Todo-App definiert eine TodoItemDatabase Klasse für diesen Zweck.

Verzögerte Initialisierung

Die TodoItemDatabase verwendet die asynchrone Lazy-Initialisierung, die durch die benutzerdefinierte AsyncLazy<T> Klasse dargestellt wird, um die Initialisierung der Datenbank zu verzögern, bis der erste Zugriff erfolgt:

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

    //...
}

Das Instance Feld wird verwendet, um die Datenbanktabelle für das TodoItem Objekt zu erstellen, sofern es noch nicht vorhanden ist, und gibt ein TodoItemDatabase als Singleton zurück. Das Instance Feld vom Typ AsyncLazy<TodoItemDatabase> wird erstellt, wenn es zum ersten Mal erwartet wird. Wenn mehrere Threads versuchen, gleichzeitig auf das Feld zuzugreifen, verwenden alle die einzelne Konstruktion. Wenn die Konstruktion abgeschlossen ist, werden alle await Vorgänge abgeschlossen. Darüber hinaus werden alle await Vorgänge nach Abschluss der Konstruktion sofort fortgesetzt, da der Wert verfügbar ist.

Hinweis

Die Datenbankverbindung ist ein statisches Feld, das sicherstellt, dass eine einzelne Datenbankverbindung für die Lebensdauer der App verwendet wird. Die Verwendung einer persistenten, statischen Verbindung bietet eine bessere Leistung als das mehrmalse Öffnen und Schließen von Verbindungen während einer einzelnen App-Sitzung.

Asynchrone Lazy-Initialisierung

Um die Datenbankinitialisierung zu starten, die Blockierung der Ausführung zu vermeiden und die Möglichkeit zu haben, Ausnahmen abzufangen, verwendet die Beispielanwendung eine asynchrone Lazy-Initalisierung, die durch die AsyncLazy<T> -Klasse dargestellt wird:

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

Die AsyncLazy -Klasse kombiniert die Lazy<T> Typen und Task<T> , um eine lazy-initialisierte Aufgabe zu erstellen, die die Initialisierung einer Ressource darstellt. Der Factorydelegat, der an den Konstruktor übergeben wird, kann entweder synchron oder asynchron sein. Factorydelegaten werden in einem Threadpoolthread ausgeführt und nicht mehr als einmal ausgeführt (auch wenn mehrere Threads versuchen, sie gleichzeitig zu starten). Wenn ein Factorydelegat abgeschlossen ist, ist der lazy-initialisierte Wert verfügbar, und alle Methoden, die auf den AsyncLazy<T> instance warten, erhalten den Wert. Weitere Informationen finden Sie unter AsyncLazy.

Datenbearbeitungsmethoden

Die TodoItemDatabase -Klasse enthält Methoden für die vier Arten der Datenbearbeitung: Erstellen, Lesen, Bearbeiten und Löschen. Die SQLite.NET-Bibliothek bietet eine einfache Objekt relationale Zuordnung (Object Relational Map, ORM), mit der Sie Objekte speichern und abrufen können, ohne SQL-Anweisungen zu schreiben.

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

Zugreifen auf Daten in Xamarin.Forms

Die TodoItemDatabase -Klasse macht das Instance Feld verfügbar, über das die Datenzugriffsvorgänge in der TodoItemDatabase -Klasse aufgerufen werden können:

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

Erweiterte Konfiguration

SQLite bietet eine robuste API mit mehr Features, als in diesem Artikel und in der Beispiel-App behandelt werden. In den folgenden Abschnitten werden Features behandelt, die für die Skalierbarkeit wichtig sind.

Weitere Informationen finden Sie in der SQLite-Dokumentation zu sqlite.org.

Schreibvorgangsprotokollierung

Standardmäßig verwendet SQLite ein herkömmliches Rollbackjournal. Eine Kopie des unveränderten Datenbankinhalts wird in eine separate Rollbackdatei geschrieben, dann werden die Änderungen direkt in die Datenbankdatei geschrieben. Der COMMIT tritt auf, wenn das Rollbackjournal gelöscht wird.

Write-Ahead Logging (WAL) schreibt Änderungen zuerst in eine separate WAL-Datei. Im WAL-Modus ist ein COMMIT ein spezieller Datensatz, der an die WAL-Datei angefügt wird, sodass mehrere Transaktionen in einer einzigen WAL-Datei ausgeführt werden können. Eine WAL-Datei wird in einem speziellen Vorgang namens Prüfpunkt wieder in die Datenbankdatei zusammengeführt.

WAL kann für lokale Datenbanken schneller sein, da Leser und Autoren sich nicht gegenseitig blockieren, sodass Lese- und Schreibvorgänge gleichzeitig ausgeführt werden können. Der WAL-Modus lässt jedoch keine Änderungen an der Seitengröße zu, fügt der Datenbank zusätzliche Dateizuordnungen hinzu und fügt den zusätzlichen Prüfpunktvorgang hinzu.

Um WAL in SQLite.NET zu aktivieren, rufen Sie die EnableWriteAheadLoggingAsync -Methode auf der SQLiteAsyncConnection instance auf:

await Database.EnableWriteAheadLoggingAsync();

Weitere Informationen finden Sie unter SQLite Write-Ahead Logging on sqlite.org.

Kopieren einer Datenbank

Es gibt mehrere Fälle, in denen es erforderlich sein kann, eine SQLite-Datenbank zu kopieren:

  • Eine Datenbank ist im Lieferumfang Ihrer Anwendung enthalten, muss jedoch kopiert oder in den schreibbaren Speicher auf dem mobilen Gerät verschoben werden.
  • Sie müssen eine Sicherung oder Kopie der Datenbank erstellen.
  • Sie müssen die Datenbankdatei versionieren, verschieben oder umbenennen.

Im Allgemeinen ist das Verschieben, Umbenennen oder Kopieren einer Datenbankdatei derselbe Prozess wie jeder andere Dateityp mit einigen zusätzlichen Überlegungen:

  • Alle Datenbankverbindungen sollten geschlossen werden, bevor Sie versuchen, die Datenbankdatei zu verschieben.
  • Wenn Sie die Schreibzugriffsprotokollierung verwenden, erstellt SQLite eine Shared Memory Access-Datei (.shm) und eine (Write Ahead Log) (.wal)-Datei. Stellen Sie sicher, dass Sie alle Änderungen auch auf diese Dateien anwenden.

Weitere Informationen finden Sie unter Dateibehandlung in Xamarin.Forms.