Xamarin.Forms Lokale Datenbanken
Das SQLite-Datenbankmodul ermöglicht Xamarin.Forms Anwendungen das Laden und Speichern von Datenobjekten in gemeinsam genutztem Code. Die Beispielanwendung verwendet eine SQLite-Datenbanktabelle zum Speichern von Todoelementen. In diesem Artikel wird beschrieben, wie Sie SQLite.Net im freigegebenen Code verwenden, um Informationen in einer lokalen Datenbank zu speichern und abzurufen.
Integrieren Sie SQLite.NET in mobile Apps, indem Sie die folgenden Schritte ausführen:
- Installieren Sie das NuGet-Paket.
- Konfigurieren Sie Konstanten.
- Erstellen Sie eine Datenbankzugriffsklasse.
- Zugreifen auf Daten in Xamarin.Forms.
- Erweiterte Konfiguration
Installieren Sie das SQLite NuGet-Paket
Verwenden Sie den NuGet-Paket-Manager, um nach sqlite-net-pcl zu suchen und die neueste Version zum freigegebenen Codeprojekt 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 die 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 Standardenumwerte SQLiteOpenFlag
an, die zum Initialisieren der Datenbankverbindung verwendet werden. Die SQLiteOpenFlag
-Enumeration unterstützt diese 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 sie aktiviert ist.ReadWrite
: Die Verbindung kann Daten lesen und schreiben.SharedCache
: Die Verbindung nimmt am freigegebenen Cache teil, wenn sie 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 nach wie vor zugänglich, wenn die oder der Benutzende das Gerät sperrt.ProtectionCompleteUntilFirstUserAuthentication
: Die Datei wird verschlüsselt, bis die oder der Benutzende das Gerät gestartet und entsperrt hat.ProtectionNone
: Die Datenbankdatei ist nicht verschlüsselt.
Je nachdem, wie die Datenbank verwendet wird, müssen Sie möglicherweise unterschiedliche Flags angeben. Weitere Informationen SQLiteOpenFlags
finden Sie unter Neue Datenbankverbindung öffnen auf sqlite.org.
Datenbankzugriffsklasse erstellen
Eine Datenbankwrapperklasse abstrahiert die Datenzugriffsebene von der restlichen App. Diese Klasse zentralisiert die Abfragelogik und vereinfacht die Verwaltung der Datenbankinitialisierung, sodass es einfacher ist, Datenoperationen zu refaktorisieren oder zu erweitern, wenn die App wächst. Die Todo-App definiert eine TodoItemDatabase
Klasse für diesen Zweck.
Verzögerte Initialisierung
Die TodoItemDatabase
asynchrone lazy Initialisierung wird durch die benutzerdefinierte AsyncLazy<T>
Klasse dargestellt, 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
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 sie alle die einzige Konstruktion. Wenn der Bau 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 dauerhaften, statischen Verbindung bietet eine bessere Leistung als das Öffnen und Schließen von Verbindungen mehrmals während einer einzelnen App-Sitzung.
Asynchrone faule Initialisierung
Um die Datenbankinitialisierung zu starten, die Ausführung zu vermeiden und die Möglichkeit zu haben, Ausnahmen abzufangen, verwendet die Beispielanwendung asynchrone Faulinitalisierung, dargestellt durch die AsyncLazy<T>
Klasse:
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 und Task<T>
die Lazy<T>
Typen, um einen lazy-initialisierten Vorgang zu erstellen, der die Initialisierung einer Ressource darstellt. Der Factorydelegat, der an den Konstruktor übergeben wird, kann synchron oder asynchron sein. Factorydelegats 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 die AsyncLazy<T>
Instanz warten, erhalten den Wert. Weitere Informationen finden Sie unter AsyncLazy.
Methoden zur Datenmanipulation
Die Klasse TodoItemDatabase
enthält Methoden für die vier Arten der Datenmanipulation: Erstellen, Lesen, Bearbeiten und Löschen. Die SQLite.NET-Bibliothek bietet eine einfache Object Relational Map (ORM), mit der das Speichern und Abrufen von Objekten ohne das Schreiben von SQL-Anweisungen möglich ist.
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 Funktionen als in diesem Artikel und der Beispiel-App behandelt werden. Die folgenden Abschnitte behandeln Funktionen, die für die Skalierbarkeit wichtig sind.
Weitere Informationen finden Sie in der SQLite- Dokumentation unter sqlite.org.
Write-Ahead-Protokollierung
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. Das COMMIT erfolgt, wenn das Rollbackjournal gelöscht wird.
Write-Ahead-Protokollierung (WAL) schreibt Änderungen zunächst in eine separate WAL-Datei. Im WAL-Modus ist ein COMMIT ein spezieller Datensatz, der an die WAL-Datei angehängt wird und der es ermöglicht, mehrere Transaktionen in einer einzigen WAL-Datei durchzuführen. Eine WAL-Datei wird in einem speziellen Vorgang, dem Prüfpunkt, wieder mit der Datenbankdatei zusammengeführt.
WAL kann für lokale Datenbanken schneller sein, da sich Lesende und Schreibende nicht gegenseitig blockieren, sodass Lese- und Schreibvorgänge gleichzeitig ausgeführt werden können. Der WAL-Modus erlaubt jedoch keine Änderung der Seitengröße, fügt der Datenbank zusätzliche Dateiverknüpfungen hinzu und bietet einen zusätzlichen Prüfpunkt-Vorgang.
Um WAL in SQLite.NET zu aktivieren, rufen Sie die EnableWriteAheadLoggingAsync
-Methode für die SQLiteAsyncConnection
-Instanz auf:
await Database.EnableWriteAheadLoggingAsync();
Weitere Informationen finden Sie in SQLite- Write-Ahead-Protokollierung unter sqlite.org..
Kopieren einer Datenbank
Es gibt mehrere Fälle, in denen es notwendig sein kann, eine SQLite-Datenbank zu kopieren:
- Eine Datenbank wurde mit Ihrer Anwendung geliefert, muss aber in einen beschreibbaren Speicher auf dem mobilen Gerät kopiert oder verschoben werden.
- Sie benötigen eine Sicherung oder Kopie der Datenbank.
- Sie müssen die Datenbankdatei versionieren, verschieben oder umbenennen.
Im Allgemeinen ist das Verschieben, Umbenennen oder Kopieren einer Datenbankdatei der gleiche Vorgang wie bei jedem anderen Dateityp, mit einigen zusätzlichen Aspekten:
- Vor dem Verschieben der Datenbankdatei sollten alle Datenbankverbindungen geschlossen werden.
- Wenn Sie Write-Ahead-Protokollierung verwenden, erstellt SQLite eine Shared Memory Access (.shm)-Datei 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.