Udostępnij przez


Lokalne bazy danych programu .NET MAUI

Przeglądaj przykład. Przeglądanie przykładu

Silnik bazy danych SQLite umożliwia aplikacjom .NET MAUI ładowanie i zapisywanie obiektów danych we wspólnym kodzie. Możesz zintegrować SQLite.NET z aplikacjami .NET MAUI, aby przechowywać i pobierać informacje w lokalnej bazie danych, wykonując następujące kroki:

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

W tym artykule użyto pakietu NuGet sqlite-net-pcl w celu zapewnienia dostępu bazy danych SQLite do tabeli do przechowywania zadań do wykonania. Alternatywą jest użycie pakietu NuGet Microsoft.Data.Sqlite , który jest uproszczonym dostawcą ADO.NET dla biblioteki SQLite. Microsoft.Data.Sqlite implementuje typowe abstrakcje ADO.NET dla funkcji, takich jak połączenia, polecenia i czytniki danych.

Instalowanie pakietu NuGet SQLite

Użyj menedżera pakietów NuGet, aby wyszukać pakiet sqlite-net-pcl i dodać najnowszą wersję do projektu aplikacji .NET MAUI.

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 do NuGet:sqlite-net-pcl

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

Ważne

SQLite.NET to zewnętrzna biblioteka obsługiwana z praeclarum/sqlite-net repo.

Konfigurowanie stałych aplikacji

Dane konfiguracji, takie jak nazwa pliku bazy danych i ścieżka, mogą być przechowywane jako stałe w 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 =>
        Path.Combine(FileSystem.AppDataDirectory, DatabaseFilename);
}

W tym przykładzie 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 zserializowanym trybie wątkowym.
  • NoMutex: Połączenie jest otwierane w trybie wielowątkowym.
  • PrivateCache: Połączenie nie będzie uczestniczyć w udostępnionej pamięci podręcznej, nawet jeśli jest ona włączona.
  • ReadWrite: Połączenie może odczytywać i zapisywać dane.
  • SharedCache: Połączenie będzie uczestniczyć w udostępnionej pamięci podręcznej, jeśli ta opcja jest włączona.
  • 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 SQLiteOpenFlags, zobacz Otwieranie nowego połączenia z bazą danych na sqlite.org.

Utwórz klasę TodoItem

Przed kodowaniem dostępu do bazy danych utwórz klasę, która będzie przechowywać dane.

public class TodoItem
{
    [PrimaryKey, AutoIncrement]
    public int ID { get; set; }
    public string Name { get; set; }
    public string Notes { get; set; }
    public bool Done { get; set; }
}

Identyfikator w tej klasie będzie służył jako klucz podstawowy, który jest automatycznie inkrementowany podczas zapisu. Atrybuty mogą służyć do określania tego zachowania.

Tworzenie klasy dostępu do bazy danych

Klasa opakowująca bazę danych abstrakcyjnie oddziela warstwę dostępu do danych od reszty 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. Przykładowa aplikacja definiuje klasę TodoItemDatabase w tym celu.

Leniwe inicjalizowanie

Element TodoItemDatabase używa asynchronicznej leniwej inicjalizacji, aby opóźnić inicjalizację bazy danych do momentu, gdy zostanie ona po raz pierwszy uzyskana za pomocą prostej metody Init, która jest wywoływana przez każdą metodę w klasie.

public class TodoItemDatabase
{
    SQLiteAsyncConnection database;

    async Task Init()
    {
        if (database is not null)
            return;

        database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
        var result = await database.CreateTableAsync<TodoItem>();
    }
    ...
}

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.

W poniższym przykładzie przedstawiono metody manipulowania danymi w przykładowej aplikacji:

public class TodoItemDatabase
{
    ...
    public async Task<List<TodoItem>> GetItemsAsync()
    {
        await Init();
        return await database.Table<TodoItem>().ToListAsync();
    }

    public async Task<List<TodoItem>> GetItemsNotDoneAsync()
    {
        await Init();
        return await database.Table<TodoItem>().Where(t => t.Done).ToListAsync();

        // SQL queries are also possible
        //return await Database.QueryAsync<TodoItem>("SELECT * FROM [TodoItem] WHERE [Done] = 0");
    }

    public async Task<TodoItem> GetItemAsync(int id)
    {
        await Init();
        return await database.Table<TodoItem>().Where(i => i.ID == id).FirstOrDefaultAsync();
    }

    public async Task<int> SaveItemAsync(TodoItem item)
    {
        await Init();
        if (item.ID != 0)
            return await database.UpdateAsync(item);
        else
            return await database.InsertAsync(item);
    }

    public async Task<int> DeleteItemAsync(TodoItem item)
    {
        await Init();
        return await database.DeleteAsync(item);
    }
}

Uzyskiwanie dostępu do danych

Klasę TodoItemDatabase można zarejestrować jako pojedynczą, która może być używana w całej aplikacji, jeśli używasz iniekcji zależności. Możesz na przykład zarejestrować strony i klasę dostępu bazy danych jako usługi na obiekcie IServiceCollection w MauiProgram.cs za pomocą metod AddSingleton i AddTransient:

builder.Services.AddSingleton<TodoListPage>();
builder.Services.AddTransient<TodoItemPage>();

builder.Services.AddSingleton<TodoItemDatabase>();

Te usługi można następnie automatycznie wprowadzać do konstruktorów klas i uzyskiwać do nich dostęp:

TodoItemDatabase database;

public TodoItemPage(TodoItemDatabase todoItemDatabase)
{
    InitializeComponent();
    database = todoItemDatabase;
}

async void OnSaveClicked(object sender, EventArgs e)
{
    if (string.IsNullOrWhiteSpace(Item.Name))
    {
        await DisplayAlert("Name Required", "Please enter a name for the todo item.", "OK");
        return;
    }

    await database.SaveItemAsync(Item);
    await Shell.Current.GoToAsync("..");
}
TodoItemDatabase database;

public TodoItemPage(TodoItemDatabase todoItemDatabase)
{
    InitializeComponent();
    database = todoItemDatabase;
}

async void OnSaveClicked(object sender, EventArgs e)
{
    if (string.IsNullOrWhiteSpace(Item.Name))
    {
        await DisplayAlertAsync("Name Required", "Please enter a name for the todo item.", "OK");
        return;
    }

    await database.SaveItemAsync(Item);
    await Shell.Current.GoToAsync("..");
}

Alternatywnie można utworzyć nowe wystąpienia klasy dostępu bazy danych:

TodoItemDatabase database;

public TodoItemPage()
{
    InitializeComponent();
    database = new TodoItemDatabase();
}

Aby uzyskać więcej informacji na temat wstrzykiwania zależności w aplikacjach .NET MAUI, zobacz Wstrzykiwanie zależności.

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 na stronie 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 następuje w momencie usunięcia dziennika wycofywania.

Funkcja Write-Ahead Logging (WAL) najpierw zapisuje zmiany w osobnym pliku WAL. W trybie WAL, instrukcja KOMIT jest specjalnym rekordem dołączonym do pliku WAL, co pozwala na wykonywanie wielu transakcji w jednym pliku WAL. Plik WAL jest ponownie scalany z plikiem bazy danych w 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 na sqlite.org (Rejestrowanie przed zapisem).

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 zaktualizować, 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.