Condividi tramite


Database locali .NET MAUI

Browse sample. Esplorare l'esempio

Il motore di database SQLite consente alle app .NET multipiattaforma (.NET MAUI) di caricare e salvare oggetti dati nel codice condiviso. È possibile integrare SQLite.NET nelle app MAUI .NET per archiviare e recuperare informazioni in un database locale, seguendo questa procedura:

  1. Installare il pacchetto NuGet.
  2. Configurare le costanti.
  3. Creare una classe di accesso al database.
  4. Accedere ai dati.
  5. Configurazione avanzata.

Questo articolo usa il pacchetto NuGet sqlite-net-pcl per fornire al database SQLite l'accesso a una tabella per archiviare gli elementi todo. Un'alternativa consiste nell'usare il pacchetto NuGet Microsoft.Data.Sqlite, che è un provider di ADO.NET leggero per SQLite. Microsoft.Data.Sqlite implementa le astrazioni comuni ADO.NET per funzionalità quali connessioni, comandi e lettori di dati.

Installare il pacchetto NuGet SQLite

Usare gestione pacchetti NuGet per cercare il pacchetto sqlite-net-pcl e aggiungere la versione più recente al progetto di app MAUI .NET.

Esiste una serie di pacchetti NuGet con nomi simili. Il pacchetto corretto ha questi attributi:

  • ID: sqlite-net-pcl
  • Autori: SQLite-net
  • Proprietari: praeclarum
  • Collegamento NuGet:sqlite-net-pcl

Nonostante il nome del pacchetto, usare il pacchetto NuGet sqlite-net-pcl nei progetti MAUI .NET.

Importante

SQLite.NET è una libreria di terze parti supportata dal repository praeclarum/sqlite-net.

Installare SQLitePCLRaw.bundle_green

Oltre a sqlite-net-pcl, è necessario installare temporaneamente la dipendenza sottostante che espone SQLite in ogni piattaforma:

  • ID: SQLitePCLRaw.bundle_green
  • Versione:>= 2.1.0
  • Autori: Eric Sink
  • Proprietari: Eric Sink
  • Collegamento NuGet:SQLitePCLRaw.bundle_green

Configurare le costanti dell'app

I dati di configurazione, ad esempio il nome file e il percorso del database, possono essere archiviati come costanti nell'app. Il progetto di esempio include un file Constants.cs che fornisce dati di configurazione comuni:

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

In questo esempio, il file di costanti specifica i valori di enumerazione predefiniti SQLiteOpenFlag utilizzati per inizializzare la connessione al database. L'enumerazione SQLiteOpenFlag supporta questi valori:

  • Create: la connessione creerà automaticamente il file di database, se non esiste.
  • FullMutex: la connessione viene aperta in modalità di threading serializzato.
  • NoMutex: la connessione viene aperta in modalità multithreading.
  • PrivateCache: la connessione non parteciperà alla cache condivisa, anche se è abilitata.
  • ReadWrite: la connessione può leggere e scrivere dati.
  • SharedCache: la connessione parteciperà alla cache condivisa, se abilitata.
  • ProtectionComplete: il file è crittografato e inaccessibile mentre il dispositivo è bloccato.
  • ProtectionCompleteUnlessOpen: il file viene crittografato fino all'apertura, ma è accessibile anche se l'utente blocca il dispositivo.
  • ProtectionCompleteUntilFirstUserAuthentication: il file viene crittografato fino a quando l'utente non ha avviato e sbloccato il dispositivo.
  • ProtectionNone: il file di database non è crittografato.

Potrebbe essere necessario specificare flag diversi a seconda della modalità di utilizzo del database. Per altre informazioni su SQLiteOpenFlags, vedere Apertura di un nuovo Connessione database in sqlite.org.

Creare una classe di accesso al database

Una classe wrapper del database astrae il livello di accesso ai dati dal resto dell'app. Questa classe centralizza la logica di query e semplifica la gestione dell'inizializzazione del database, semplificando il refactoring o l'espansione delle operazioni sui dati man mano che l'app cresce. L'app di esempio definisce una TodoItemDatabase classe a questo scopo.

Inizializzazione differita

TodoItemDatabase usa l'inizializzazione differita asincrona per ritardare l'inizializzazione del database fino alla prima accesso, con un metodo semplice Init che viene chiamato da ogni metodo nella classe :

public class TodoItemDatabase
{
    SQLiteAsyncConnection Database;

    public TodoItemDatabase()
    {
    }

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

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

Metodi di manipolazione dei dati

La TodoItemDatabase classe include metodi per i quattro tipi di manipolazione dei dati: creazione, lettura, modifica ed eliminazione. La libreria SQLite.NET fornisce una semplice mappa relazionale a oggetti (ORM) che consente di archiviare e recuperare oggetti senza scrivere istruzioni SQL.

L'esempio seguente illustra i metodi di manipolazione dei dati nell'app di esempio:

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

Accedere ai dati

La TodoItemDatabase classe può essere registrata come singleton che può essere usata in tutta l'app se si usa l'inserimento delle dipendenze. Ad esempio, è possibile registrare le pagine e la classe di accesso al database come servizi nell'oggetto , in MauiProgram.cs, con i AddSingleton metodi e AddTransient :IServiceCollection

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

builder.Services.AddSingleton<TodoItemDatabase>();

Questi servizi possono quindi essere inseriti automaticamente nei costruttori di classi e accessibili:

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

In alternativa, è possibile creare nuove istanze della classe di accesso al database:

TodoItemDatabase database;

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

Per altre informazioni sull'inserimento delle dipendenze nelle app MAUI .NET, vedere Inserimento delle dipendenze.

Configurazione avanzata

SQLite offre un'API affidabile con più funzionalità di quelle descritte in questo articolo e nell'app di esempio. Le sezioni seguenti illustrano le funzionalità importanti per la scalabilità.

Per altre informazioni, vedere la documentazione di SQLite su sqlite.org.

Registrazione write-ahead

Per impostazione predefinita, SQLite usa un journal di rollback tradizionale. Una copia del contenuto del database invariato viene scritta in un file di rollback separato, quindi le modifiche vengono scritte direttamente nel file di database. Commit si verifica quando viene eliminato il journal di rollback.

La registrazione write-ahead scrive prima le modifiche in un file WAL separato. In modalità WAL, commit è un record speciale, aggiunto al file WAL, che consente l'esecuzione di più transazioni in un singolo file WAL. Un file WAL viene unito di nuovo nel file di database in un'operazione speciale denominata checkpoint.

Wal può essere più veloce per i database locali perché i lettori e i writer non si bloccano tra loro, consentendo operazioni di lettura e scrittura simultanee. Tuttavia, la modalità WAL non consente modifiche alle dimensioni della pagina, aggiunge altre associazioni di file al database e aggiunge l'operazione di checkpoint aggiuntiva.

Per abilitare WAL in SQLite.NET, chiamare il metodo nell'istanza EnableWriteAheadLoggingAsyncSQLiteAsyncConnection di :

await Database.EnableWriteAheadLoggingAsync();

Per altre informazioni, vedere Registrazione write-ahead di SQLite in sqlite.org.

Copiare un database

Esistono diversi casi in cui potrebbe essere necessario copiare un database SQLite:

  • Un database è stato fornito con l'applicazione, ma deve essere copiato o spostato nello spazio di archiviazione scrivibile nel dispositivo mobile.
  • È necessario eseguire un backup o una copia del database.
  • È necessario aggiornare, spostare o rinominare il file di database.

In generale, lo spostamento, la ridenominazione o la copia di un file di database è lo stesso processo di qualsiasi altro tipo di file con alcune considerazioni aggiuntive:

  • Tutte le connessioni di database devono essere chiuse prima di tentare di spostare il file di database.
  • Se si usa la registrazione write-ahead, SQLite creerà un file con estensione shm (Shared Memory Access) e un file (write ahead log) (con estensione wal). Assicurarsi di applicare anche le modifiche apportate a questi file.