Archiviare i dati in locale con SQLite

Completato

L'archiviazione dei dati in SQLite è utile quando sono presenti dati relazionali. Si supponga di creare un'app di social media. È necessario archiviare le informazioni relative agli abbonati all'app. Tali dati includono un ID univoco per ogni utente e il relativo nome. È possibile modellare facilmente questo tipo di relazione in un database SQLite.

Questa unità descrive come usare SQLite in un'applicazione .NET MAUI tramite SQLite-net.

Che cos'è SQLite?

SQLite è un database locale multipiattaforma leggero che è diventato uno standard di settore per le applicazioni per dispositivi mobili. SQLite non richiede un server. Il database è archiviato in un unico file su disco nel file system del dispositivo. Tutte le operazioni di lettura e scrittura vengono eseguite direttamente sulla base del file su disco di SQLite.

Le librerie native SQLite sono integrate in Android e iOS per impostazione predefinita; Tuttavia, il motore supporta solo un'API C/C++. Questo scenario non è ideale per gli sviluppatori .NET, che vogliono ottenere una certa interazione tra SQLite e .NET.

Cos'è SQLite-net?

Esistono diversi wrapper C# basati sul motore SQLite nativo che gli sviluppatori .NET possono usare. Molti sviluppatori .NET usano un wrapper C# diffuso denominato SQLite-net.

SQLite-net è un mapper relazionale a oggetti. Semplifica il processo di definizione degli schemi di database consentendo di usare i modelli definiti nei progetti come schema.

Diagram showing how SQLite-net provides a .NET wrapper and the SQLite C/C++ engine.

Si consideri ad esempio la classe seguente che modella un User:

class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    ...
}

Usando un mapper relazionale a oggetti, è possibile accettare questa classe User iniziale e creare una tabella di database denominata User con colonne per i campi Id e Username in questa classe.

SQLite-net è disponibile come pacchetto NuGet. È necessario aggiungere il pacchetto sqlite-net-pcl alle app per usarlo. Usare la gestione pacchetti di NuGet in Visual Studio. Se si intende eseguire un'app in Android, è necessario anche aggiungere il pacchetto SQLitePCLRaw.provider.dynamic_cdecl.

Come connettersi a un database SQLite

È possibile stabilire una connessione a un database SQLite da un'app tramite un oggetto SQLiteConnection. Questa classe viene definita nello spazio dei nomi SQLite, insieme agli altri tipi e metodi forniti da SQLite. Quando si crea un'istanza di questo oggetto, passare il nome del file di database. Il costruttore apre il file, se esiste, in caso contrario lo crea.

Il codice seguente visualizza un esempio:

using SQLite;
...
string filename = ...
SQLiteConnection conn = new SQLiteConnection(filename);

Tenere presente che filename deve puntare a una posizione nella sandbox dell'app.

Come creare una tabella

Ricordare che SQLite-net è un mapper relazionale a oggetti, ovvero lo schema del database può essere creato da classi C#. SQLite-net può creare una tabella di database da una normale classe C#, ma esistono molti attributi che è possibile aggiungere a una classe per fornire altri metadati. Tali metadati consentono a SQLite di applicare funzionalità, come l'univocità, e di applicare vincoli ai dati.

Gli attributi disponibili includono:

  • Table: Specificare il nome della tabella se si vuole usare un nome diverso da quello della classe.
  • PrimaryKey: Specificare che una colonna è la chiave primaria
  • AutoIncrement: Specificare che il valore di una colonna deve aumentare automaticamente quando viene inserita una nuova riga
  • Column: Specificare il nome di una colonna se si vuole usare un nome diverso da quello della proprietà
  • MaxLength: Specificare il numero massimo di caratteri che possono essere usati nella colonna
  • Unique: Specificare che il valore della colonna deve essere univoco tra tutte le altre righe

Il codice seguente mostra una versione aggiornata della classe User che applica questi attributi:

[Table("user")]
public class User
{
    // PrimaryKey is typically numeric 
    [PrimaryKey, AutoIncrement, Column("_id")]
    public int Id { get; set; }

    [MaxLength(250), Unique]
    public string Username { get; set; }
    ...
}

Dopo aver definito la classe C#, chiamare il metodo generico CreateTable nella classe SQLiteConnection per generare la tabella nel database. Specificare la classe come parametro di tipo. Ecco un esempio:

SQLiteConnection conn = new SQLiteConnection(filename);
conn.CreateTable<User>();

Se la tabella esiste già nel database, il metodo CreateTable controlla lo schema per verificare se sono presenti delle modifiche. In caso affermativo, l'operazione tenta di aggiornare lo schema del database.

Come eseguire operazioni di lettura e scrittura di base

Dopo aver creato una tabella, è possibile iniziare a interagire con essa. Per aggiungere una riga, utilizzare il metodo Insert nell'istanza di SQLiteConnection e fornire un oggetto del tipo appropriato che contiene i dati da inserire. Il codice seguente illustra come aggiungere una nuova riga alla tabella User:

public int AddNewUser(User user)
{
    int result = conn.Insert(user);
    return result;
}

Il metodo Insert restituisce un int, che rappresenta il numero di righe inserite nella tabella. In questo caso il numero è uno.

Per recuperare righe da una tabella, utilizzare il metodo Table. Questo metodo restituisce una raccolta di oggetti (che potrebbero essere vuoti):

public List<User> GetAllUsers()
{
    List<User> users = conn.Table<User>().ToList();
    return users;
}

Il metodo Table restituisce un oggetto TableQuery\<T>. Per ottenere un List, usare il metodo ToList come illustrato nell'esempio precedente.

Eseguire una query SQLite usando LINQ

Il metodo Table recupera tutte le righe da una tabella. Nella maggior parte dei casi si vuole restituire solo un subset delle righe che corrispondono a un set di criteri specificati. Per queste attività, usare LINQ con SQLite-net.

SQLite-net supporta molte query LINQ comuni tra cui:

  • Where
  • Take
  • Ignora
  • OrderBy
  • OrderByDescending
  • ThenBy
  • ElementAt
  • First
  • FirstOrDefault
  • ThenByDescending
  • Count

Con questi metodi, è possibile usare la sintassi del metodo di estensione o la sintassi C# LINQ. Ecco ad esempio un frammento di codice che consente di recuperare i dettagli di un utente specificato:

public User GetByUsername(string username)
{
    var user = from u in conn.Table<User>()
               where u.Username == username
               select u;
    return user.FirstOrDefault();
}

Aggiornare ed eliminare righe

Aggiornare una riga usando il metodo Update dell'oggetto SQLiteConnection. Si fornisce un oggetto che definisce la riga da aggiornare con nuovi valori. Il metodo Update modifica la riga con lo stesso valore della chiave primaria dell'oggetto fornito. Il valore restituito è il numero di righe modificate. Se questo valore è zero, non sono state trovate righe con una chiave primaria corrispondente e non è stato eseguito alcun aggiornamento. Il frammento successivo mostra questo metodo in azione:

public int UpdateUser(User user)
{
    int result = 0;
    result = conn.Update(user);
    return result;
}

Rimuovere righe da una tabella con il metodo Delete dell'oggetto SQLiteConnection. La forma più semplice di questo metodo accetta la chiave primaria dell'elemento da eliminare come parametro, come illustrato nell'esempio seguente. Questo formato del metodo Delete è generico e richiede un parametro di tipo. Il valore restituito è il numero di righe rimosse dalla tabella:

public int DeleteUser(int userID)
{
    int result = 0;
    result = conn.Delete<User>(userID);
    return result;
}