Xamarin.Forms Database locali

Scaricare l'esempio Scaricare l'esempio

Il motore di database SQLite consente Xamarin.Forms alle applicazioni di 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 le SQLite.Net nel codice condiviso per archiviare e recuperare informazioni in un database locale.

Screenshot dell'app Todolist in iOS e 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. Access data in Xamarin.Forms.
  5. Configurazione avanzata.

Installare il pacchetto di NuGet SQLite

Usare gestione NuGet pacchetti 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 a NuGet:sqlite-net-pcl

Nota

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

Configurare le costanti dell'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
    {
        get
        {
            var basePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            return Path.Combine(basePath, DatabaseFilename);
        }
    }
}

Il file delle costanti specifica i valori SQLiteOpenFlag di enumerazione predefiniti usati per inizializzare la connessione al database. SQLiteOpenFlagL'enumerazione supporta questi valori:

  • Create: la connessione creerà automaticamente il file di database se non esiste.
  • FullMutex: la connessione viene aperta in modalità threading serializzata.
  • NoMutex: la connessione viene aperta in modalità multithreading.
  • PrivateCache: la connessione non farà parte della cache condivisa, anche se è abilitata.
  • ReadWrite: la connessione può leggere e scrivere dati.
  • SharedCache: la connessione farà parte della cache condivisa, se abilitata.
  • ProtectionComplete: il file è crittografato e inaccessibile mentre il dispositivo è bloccato.
  • ProtectionCompleteUnlessOpen: il file viene crittografato fino a quando non viene aperto, ma è quindi 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 una nuova connessione di SQLiteOpenFlags 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

Usa TodoItemDatabase l'inizializzazione differita asincrona, rappresentata dalla classe personalizzata, per ritardare l'inizializzazione del database fino al AsyncLazy<T> 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 campo viene usato per creare la tabella di database per l'oggetto, se non esiste già, e restituisce un come InstanceTodoItemTodoItemDatabase singleton. Il Instance campo di tipo viene costruito la prima volta che viene AsyncLazy<TodoItemDatabase> atteso. Se più thread tentano di accedere al campo contemporaneamente, useranno tutti la singola costruzione. Quindi, al termine della costruzione, tutte await le operazioni vengono completate. Inoltre, tutte le await operazioni dopo il completamento della costruzione continuano immediatamente perché il valore è disponibile.

Nota

La connessione al database è un campo statico che garantisce l'uso di una singola connessione di database per la durata dell'app. L'uso di una connessione statica permanente 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 il blocco dell'esecuzione e avere la possibilità di rilevare le eccezioni, l'applicazione di esempio usa l'inizializzazione 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 tipi e per creare Lazy<T>Task<T> un'attività inizializzata lazy che rappresenta l'inizializzazione di una risorsa. Il delegato factory passato al costruttore può essere sincrono o asincrono. I delegati factory verranno eseguiti in un thread del pool di thread e non verranno eseguiti più di una volta, 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 AsyncLazy<T> dell'istanza 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 SQLite.NET fornisce una semplice mappa relazionale a oggetti (ORM) che consente di archiviare e recuperare oggetti senza scrivere SQL istruzioni.

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 classe espone il campo tramite il quale è possibile richiamare le operazioni di accesso ai dati TodoItemDatabaseInstance nella classe TodoItemDatabase :

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 illustrate 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 sqlite.org.

Registrazione write-ahead

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

Write-Ahead logging (WAL) scrive prima le modifiche in un file WAL separato. In modalità WAL, un 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 a vicenda, consentendo di eseguire operazioni di lettura e scrittura simultanee. Tuttavia, la modalità WAL non consente modifiche alle dimensioni della pagina,aggiunge associazioni di file aggiuntive al database e aggiunge l'operazione di checkpoint aggiuntiva.

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

await Database.EnableWriteAheadLoggingAsync();

Per altre informazioni, vedere SQLite Write-Ahead logging on sqlite.org.

Copiare un database

In alcuni casi può essere necessario copiare un database SQLite:

  • Un database è stato fornito con l'applicazione, ma deve essere copiato o spostato nell'archiviazione scrivibile nel dispositivo mobile.
  • È necessario eseguire un backup o una copia del database.
  • È necessario eseguire la versione, 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 di accesso alla memoria condivisa (con estensione shm) e un file (write ahead log) (con estensione wal). Assicurarsi di applicare le modifiche anche a questi file.

Per altre informazioni, vedere File Handling in Xamarin.Forms.