Udostępnij za pośrednictwem


Xamarin.Forms Lokalne bazy danych

Aparat bazy danych SQLite umożliwia Xamarin.Forms aplikacjom ładowanie i zapisywanie obiektów danych w kodzie udostępnionym. Przykładowa aplikacja używa tabeli bazy danych SQLite do przechowywania zadań do wykonania. W tym artykule opisano sposób używania SQLite.Net w kodzie udostępnionym do przechowywania i pobierania informacji w lokalnej bazie danych.

Zrzuty ekranu aplikacji Todolist w systemach iOS i Android

Zintegruj SQLite.NET z aplikacjami mobilnymi, wykonując następujące kroki:

  1. Zainstaluj pakiet NuGet.
  2. Konfigurowanie stałych.
  3. Utwórz klasę dostępu do bazy danych.
  4. Uzyskiwanie dostępu do danych w programie Xamarin.Forms.
  5. Konfiguracja zaawansowana.

Instalowanie pakietu NuGet SQLite

Użyj menedżera pakietów NuGet, aby wyszukać plik sqlite-net-pcl i dodać najnowszą wersję do projektu kodu udostępnionego.

Istnieje kilka pakietów NuGet o podobnych nazwach. Poprawny pakiet ma następujące atrybuty:

  • Identyfikator: sqlite-net-pcl
  • Autorzy: SQLite-net
  • Właściciele: praeclarum
  • Link pakietu NuGet:sqlite-net-pcl

Pomimo nazwy pakietu użyj pakietu NuGet sqlite-net-pcl nawet w projektach .NET Standard.

Ważne

SQLite.NET to biblioteka innej firmy obsługiwana przez repozytorium praeclarum/sqlite-net.

Konfigurowanie stałych aplikacji

Przykładowy projekt zawiera plik Constants.cs zawierający typowe dane konfiguracji:

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

Plik stałych określa domyślne SQLiteOpenFlag wartości wyliczenia, które są używane do inicjowania połączenia z bazą danych. Wyliczenie SQLiteOpenFlag obsługuje następujące wartości:

  • Create: Połączenie automatycznie utworzy plik bazy danych, jeśli nie istnieje.
  • FullMutex: Połączenie jest otwierane w trybie serializacji wątków.
  • NoMutex: Połączenie jest otwierane w trybie wielowątkowy.
  • PrivateCache: Połączenie nie będzie uczestniczyć w udostępnionej pamięci podręcznej, nawet jeśli jest włączone.
  • ReadWrite: Połączenie może odczytywać i zapisywać dane.
  • SharedCache: Połączenie będzie uczestniczyć w udostępnionej pamięci podręcznej, jeśli jest włączone.
  • ProtectionComplete: plik jest zaszyfrowany i niedostępny, gdy urządzenie jest zablokowane.
  • ProtectionCompleteUnlessOpen: plik jest szyfrowany, dopóki nie zostanie otwarty, ale będzie dostępny nawet wtedy, gdy użytkownik zablokuje urządzenie.
  • ProtectionCompleteUntilFirstUserAuthentication: plik jest szyfrowany do momentu uruchomienia i odblokowania urządzenia przez użytkownika.
  • ProtectionNone: plik bazy danych nie jest zaszyfrowany.

Może być konieczne określenie różnych flag w zależności od sposobu użycia bazy danych. Aby uzyskać więcej informacji na temat SQLiteOpenFlagsprogramu , zobacz Otwieranie nowej Połączenie bazy danych na sqlite.org.

Tworzenie klasy dostępu do bazy danych

Klasa otoki bazy danych abstrakcji warstwy dostępu do danych z pozostałej części aplikacji. Ta klasa centralizuje logikę zapytań i upraszcza zarządzanie inicjowaniem bazy danych, co ułatwia refaktoryzowanie lub rozszerzanie operacji na danych w miarę rozwoju aplikacji. Aplikacja Todo definiuje klasę TodoItemDatabase w tym celu.

Inicjowanie z opóźnieniem

Używa TodoItemDatabase asynchronicznej inicjowania z opóźnieniem reprezentowanego przez klasę niestandardową AsyncLazy<T> , aby opóźnić inicjowanie bazy danych do momentu uzyskania do niego dostępu:

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

    //...
}

Pole Instance jest używane do tworzenia tabeli bazy danych dla TodoItem obiektu, jeśli jeszcze nie istnieje, i zwraca wartość jako pojedynczą TodoItemDatabase . Pole Instance typu AsyncLazy<TodoItemDatabase> jest konstruowane po raz pierwszy, gdy jest oczekiwane. Jeśli wiele wątków próbuje jednocześnie uzyskać dostęp do pola, wszystkie będą używać pojedynczej konstrukcji. Następnie po zakończeniu budowy wszystkie await operacje zakończą się. Ponadto wszystkie await operacje po zakończeniu budowy są kontynuowane natychmiast, ponieważ wartość jest dostępna.

Uwaga

Połączenie z bazą danych jest polem statycznym, które zapewnia, że pojedyncze połączenie z bazą danych jest używane przez cały czas działania aplikacji. Użycie trwałego połączenia statycznego zapewnia lepszą wydajność niż wielokrotne otwieranie i zamykanie połączeń w ramach jednej sesji aplikacji.

Inicjowanie asynchroniczne z opóźnieniem

Aby rozpocząć inicjowanie bazy danych, unikać blokowania wykonywania i mieć możliwość przechwycenia wyjątków, przykładowa aplikacja używa asynchronicznej opóźnianej intalizacji reprezentowanej przez klasę AsyncLazy<T> :

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

Klasa AsyncLazy łączy Lazy<T> typy i Task<T> w celu utworzenia z opóźnieniem zainicjowanego zadania, które reprezentuje inicjowanie zasobu. Delegat fabryki przekazany do konstruktora może być synchroniczny lub asynchroniczny. Delegaty fabryki będą uruchamiane w wątku puli wątków i nie będą wykonywane więcej niż raz (nawet jeśli wiele wątków próbuje je uruchomić jednocześnie). Po zakończeniu delegata fabryki jest dostępna z opóźnieniem zainicjowana wartość, a wszystkie metody oczekujące AsyncLazy<T> na wystąpienie otrzymają wartość. Aby uzyskać więcej informacji, zobacz AsyncLazy.

Metody manipulowania danymi

Klasa TodoItemDatabase zawiera metody dla czterech typów manipulowania danymi: tworzenie, odczytywanie, edytowanie i usuwanie. Biblioteka SQLite.NET udostępnia prostą relacyjną mapę obiektów (ORM), która umożliwia przechowywanie i pobieranie obiektów bez konieczności pisania instrukcji 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);
    }
}

Uzyskiwanie dostępu do danych w Xamarin.Forms

Klasa TodoItemDatabase uwidacznia Instance pole, za pomocą którego można wywołać operacje dostępu do danych w TodoItemDatabase klasie:

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

Konfiguracja zaawansowana

SqLite udostępnia niezawodny interfejs API z większą ilością funkcji, niż opisano w tym artykule i przykładowej aplikacji. W poniższych sekcjach opisano funkcje, które są ważne dla skalowalności.

Aby uzyskać więcej informacji, zobacz dokumentację sqlite dotyczącą sqlite.org.

Rejestrowanie z wyprzedzeniem zapisu

Domyślnie sqLite używa tradycyjnego dziennika wycofywania. Kopia niezmienionej zawartości bazy danych jest zapisywana w osobnym pliku wycofywania, a następnie zmiany są zapisywane bezpośrednio w pliku bazy danych. Zatwierdzenie występuje po usunięciu dziennika wycofywania.

Funkcja write-Ahead Logging (WAL) zapisuje zmiany w osobnym pliku WAL najpierw. W trybie WAL zatwierdzenie jest specjalnym rekordem dołączonym do pliku WAL, który umożliwia wykonywanie wielu transakcji w jednym pliku WAL. Plik WAL jest scalony z powrotem do pliku bazy danych w specjalnej operacji nazywanej punktem kontrolnym.

Funkcja WAL może być szybsza w przypadku lokalnych baz danych, ponieważ czytelnicy i autorzy nie blokują się nawzajem, co pozwala na współbieżne operacje odczytu i zapisu. Jednak tryb WAL nie zezwala na zmiany rozmiaru strony, dodaje dodatkowe skojarzenia plików do bazy danych i dodaje dodatkową operację tworzenia punktów kontrolnych.

Aby włączyć funkcję WAL w SQLite.NET, wywołaj metodę EnableWriteAheadLoggingAsync w wystąpieniu SQLiteAsyncConnection :

await Database.EnableWriteAheadLoggingAsync();

Aby uzyskać więcej informacji, zobacz SQLite Write-Ahead Logging on sqlite.org (Rejestrowanie przed zapisem sqlite na sqlite.org).

Kopiowanie bazy danych

Istnieje kilka przypadków, w których może być konieczne skopiowanie bazy danych SQLite:

  • Baza danych została dostarczona z aplikacją, ale musi zostać skopiowana lub przeniesiona do magazynu zapisywalnego na urządzeniu przenośnym.
  • Należy utworzyć kopię zapasową lub kopię bazy danych.
  • Musisz wersję, przenieść lub zmienić nazwę pliku bazy danych.

Ogólnie rzecz biorąc, przenoszenie, zmienianie nazwy lub kopiowanie pliku bazy danych jest tym samym procesem co każdy inny typ pliku z kilkoma dodatkowymi zagadnieniami:

  • Przed podjęciem próby przeniesienia pliku bazy danych należy zamknąć wszystkie połączenia bazy danych.
  • Jeśli używasz funkcji Write-Ahead Logging, narzędzie SQLite utworzy plik dostępu do pamięci udostępnionej (shm) i plik (Write Ahead Log) (wal). Upewnij się, że zastosowano również wszelkie zmiany w tych plikach.

Aby uzyskać więcej informacji, zobacz Obsługa plików w programie Xamarin.Forms.