Xamarin.Forms Database locali

Download Sample Scaricare l'esempio

Il motore di database SQLite consente alle applicazioni di Xamarin.Forms caricare e salvare oggetti dati nel codice condiviso. L'applicazione di esempio usa una tabella di database SQLite per archiviare gli elementi todo. Questo articolo descrive come usare SQLite.Net nel codice condiviso per archiviare e recuperare informazioni in un database locale.

Screenshots of the Todolist app on iOS and Android

Integrare SQLite.NET nelle app per dispositivi mobili seguendo questa procedura:

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

Installare il pacchetto NuGet SQLite

Usare gestione pacchetti NuGet per cercare sqlite-net-pcl e aggiungere la versione più recente al progetto di codice condiviso.

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 anche nei progetti .NET Standard.

Importante

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

Configurare le costanti dell'app

Il progetto di esempio include un file di 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
    {
        get
        {
            var basePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            return Path.Combine(basePath, DatabaseFilename);
        }
    }
}

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 Todo definisce una TodoItemDatabase classe a questo scopo.

Inizializzazione differita

TodoItemDatabase usa l'inizializzazione differita asincrona, rappresentata dalla classe personalizzataAsyncLazy<T>, per ritardare l'inizializzazione del database fino a quando non viene eseguito il primo accesso:

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

    //...
}

Il Instance campo viene usato per creare la tabella di database per l'oggetto TodoItem , se non esiste già e restituisce come TodoItemDatabase singleton. Il Instance campo di tipo AsyncLazy<TodoItemDatabase> viene costruito la prima volta che è atteso. Se più thread tentano di accedere simultaneamente al campo, useranno tutte la singola costruzione. Al termine della costruzione, tutte le await operazioni vengono completate. Inoltre, tutte await le operazioni dopo il completamento della costruzione continuano immediatamente perché il valore è disponibile.

Nota

La connessione al database è un campo statico che garantisce che venga usata una singola connessione al database per la durata dell'app. L'uso di una connessione statica persistente offre prestazioni migliori rispetto all'apertura e alla chiusura di connessioni più volte durante una singola sessione dell'app.

Inizializzazione differita asincrona

Per avviare l'inizializzazione del database, evitare di bloccare l'esecuzione e avere la possibilità di intercettare le eccezioni, l'applicazione di esempio usa l'initalizzazione differita asincrona, rappresentata dalla AsyncLazy<T> classe :

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

La AsyncLazy classe combina i Lazy<T> tipi e Task<T> per creare un'attività inizializzata differita che rappresenta l'inizializzazione di una risorsa. Il delegato factory passato al costruttore può essere sincrono o asincrono. I delegati di factory verranno eseguiti in un thread del pool di thread e non verranno eseguiti più volte (anche quando più thread tentano di avviarli contemporaneamente). Al termine di un delegato factory, il valore inizializzato lazy è disponibile e tutti i metodi in attesa dell'istanza AsyncLazy<T> ricevono il valore. Per altre informazioni, vedere AsyncLazy.

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.

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

Accedere ai dati in Xamarin.Forms

La TodoItemDatabase classe espone il Instance campo tramite il quale è possibile richiamare le operazioni di accesso ai dati nella TodoItemDatabase classe :

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

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.

Per altre informazioni, vedere Gestione dei file in Xamarin.Forms.