Molto spesso durante lo sviluppo di un sito internet abbiamo bisogno di gestire piccole quantità di dati, tali da non giustificare l'acquisto di spazio su database Sql Server o MySql. La soluzione sicuramente più immediata, che per essere implementata, tuttavia, ci costringe a scrivere molto codice e soprattutto ci preclude l'uso di un linguaggio di interrogazione semplice, potente e flessibile come SQL, consiste nell'utilizzo di file di testo o XML come base di dati. Una soluzione alternativa e, tutto sommato, poco conosciuta risiede invece nell'uso di SQLite.
SQLite: cos'è?
SQLite è una piccola libreria scritta in C (ma di cui sono disponibili port per molti linguaggi e piattaforme) che implementa un vero e proprio motore di database. La versione stabile più recente nel momento in cui scrivo è la 3.5.1. Le principali caratteristiche di SQLite sono:
- la semplicità: nessun bisogno di installazione e configurazione, ideale quindi per soluzioni di hosting senza supporto per SQL Server e simili;
- la compattezza: un intero database è contenuto in un unico file, indipendente dal sistema operativo;
- il supporto degli standard: SQLite implementa quasi completamente il linguaggio SQL;
- la velocità: più veloce di altri database nello svolgere alcuni compiti;
- il costo: gratuito. I sorgenti sono di dominio pubblico ed utilizzabili in qualsiasi applicazione, anche commerciale.
È bene notare che, nonostante vanti molti pregi, proprio a causa della sua leggerezza SQLite porta con se anche alcuni difetti, come l'esclusione di alcune instruzioni SQL (ad esempio manca il supporto a FOREIGN KEY
e CONSTRAINT
), oppure problemi di prestazioni in scenari di utilizzo caratterizzati da accessi molto frequenti a quantità di dati di dimensione notevole.
Prima di proseguire con l'articolo penso siano d'obbligo alcune precisazioni che potranno evitarvi molti grattacapi in futuro.
SQLite 3.x supporta soltanto 5 tipi di dati: NULL
, INTEGER
, REAL
, TEXT
, BLOB
e, ad esclusione del campo Integer
con attributo PRIMARY KEY
, ogni colonna può contenere qualsiasi tipo di dato. Quindi inserire una stringa in un campo intero non genererà errori.
Per facilitare la convivenza con basi di dati più fortemente tipizzate, SQLite ha introdotto, con la versione 3.0, la Column Affinity, un sistema che trasforma automaticamente, ad esempio, un campo definito come VARCHAR
in uno TEXT
. La query:
CREATE TABLE test ( t VARCHAR(200) );
verrà quindi trasformata, senza che si verifichino errori, in:
CREATE TABLE test ( t TEXT);
Ovviamente perderemo il controllo sulla lunghezza del campo, che non sarà più di 200 caratteri. Per maggiori informazioni sulla Column Affinity vi rimando alla documentazione ufficiale.
Possiamo ovviare all'assenza di FOREIGN KEY
e CONSTRAINT
tramite i Trigger, speciali azioni che vengono eseguite al verificarsi di certe condizioni come l'inserimento, la modifica o la cancellazione di uno o più record. I trigger sono un argomento abbastanza complesso e, pertanto, non possono essere trattati in un articolo introduttivo. Per maggiori informazioni vi rimando, ancora una volta, alla sezione sui trigger della documentazione ufficiale e a questo tutorial di LinuxGazette.net.
Gestire i database
A meno che non si voglia utilizzare il prompt a riga di comando disponibile direttamente all'interno della libreria o un proprio programma, per la creazione e gestione dei database è necessario affidarsi ad un software di terze parti che fornisca un'interfaccia grafica. Facendo una rapida ricerca su internet vi accorgerete di quanti ce ne siano disponibili, alcuni gratuiti, altri a pagamento.
Personalmente quelli con cui mi sono trovato meglio sono:
- SQLiteman: è gratuito ed open source (disponibile sia per Windows che per Linux) ed offre sia funzionalità di base che funzioni più avanzate per la gestione dei nostri archivi.
- SQLite Administrator:è sempre gratuito ma, al contrario del precedente, non è open source ed è disponibile solo per sistemi Windows. Questo software si fa apprezzare soprattutto per la sua interfaccia pulita e per il suo sistema di creazione guidata dei Trigger.
- SQLite Maestro: a pagamento, è invece il prodotto che fa per voi nel caso vogliate una soluzione più professionale.
Esistono anche sistemi di gestione direttamente accessibili via web molto simili a PHP MyAdmin. Vi rimando a questa pagina per una lista abbastanza esaustiva.
ASP.Net & SQLite
Per il .Net Framework sono disponibili diverse implementazioni della libreria di base, di cui la più completa e potente è sicuramente System.Data.SQLite
sviluppata da Robert Simpson e anch'essa gratuita e di dominio pubblico. System.Data.SQLite
fornisce, tra le altre cose, una implementazione completa dello standard ADO.Net 2.0 e l'integrazione in fase di design all'interno di Visual Studio, sia per la versione completa che Express.
Della libreria sono disponibili direttamente sul sito ufficiale sia la dll già compilata, che supporta sistemi a 32 e 64 bit e la versione Compact del framework, e quindi pronta per essere utilizzata direttamente all'interno dei nostri progetti, sia il codice sorgente, che possiamo studiare, modificare e compilare a nostro piacimento nel caso avessimo esigenze più specifiche. Noi utilizzeremo, per semplicità, la versione già compilata che ci viene fornita sotto forma di pratico Windows Installer e che si preoccuperà anche di installare il plugin per il supporto in fase di design. Dopo il download, avviate l'eseguibile e seguite i semplici passi della procedura guidata.
Una prima applicazione
A chi già conosce ADO.Net l'utilizzo di System.Data.SQLite
non presenterà particolari problemi. La libreria fornisce infatti implementazioni specifiche per SQLite di tutte le classi ADO. La connessione al database viene gestita tramite SQLiteConnection
(che si preoccupa anche di creare il database nel caso non esistesse) e l'interrogazione attraverso SQLiteCommand
(con supporto ai parametri con SQLiteParameter
), mentre per leggere i dati abbiamo disponibili sia SQLiteDataReader
, adatto ad una lettura veloce ed unidirezionale dei dati, che SQLiteDataAdapter
, nel caso ci serva di una gestione più completa e potente con supporto a DataSet
e DataTable
.
L'applicazione che realizzeremo è molto semplice ed è composta da un'unica pagina, Default.aspx, che nell'evento Load
apre la connessione ad un database (test.db) che contiene la tabella Prova
, composta da due colonne: la prima, ID, di tipo Integer Primary Key
, e la seconda, Valore, di tipo Integer
. La pagina inserirà nella tabella cinque righe contenenti valori casuali e li stamperà a video leggendoli dal database.
Apriamo quindi Visual Studio o Visual Web Developer e creiamo un nuovo progetto, chiamato SqliteTest. Aggiungiamo alla soluzione la cartella speciale Bin copiandoci dentro il file System.Data.SQLite.DLL
che dovrebbe trovarsi, se avete lasciato il percorso di default durante l'installazione, in C:ProgrammiSQLite.NETbin
: la libreria è adesso disponibile all'interno dell'applicazione. Notate la dimensione molto contenuta della dll - circa 600 kb.
A questo punto apriamo (o creiamo nel caso non esistesse) la pagina Default.aspx e importiamo il namespace System.Data.Sqlite
.
Listato 1. Importazione del namespace (C#)
using System.Data.SQLite;
Inseriamo quindi nella funzione Page_Load le seguenti istruzioni:
Listato 2. Funzione Page_Load (C#)
protected void Page_Load(object sender, EventArgs e)
{
// Stringa di connessione.
// Il database verrà memorizzato all'interno della cartella App_Data del sito.
// Ovviamente avremmo potuto definirla anche all'interno del file Web.config
string connString = "Data Source = |DataDirectory|/test.db;";
// Creiamo la connessione
SQLiteConnection conn = new SQLiteConnection(connString);
try
{
// Apriamo la connessione
conn.Open();
// Creiamo l'oggetto SQLiteCommand, con cui
// verranno eseguite le query sul database.
SQLiteCommand cmd = new SQLiteCommand(conn);
// Creiamo la tabella Prova se non esiste
cmd.CommandText = "CREATE TABLE IF NOT EXISTS Prova (ID INTEGER PRIMARY KEY, Valore INTEGER)";
cmd.ExecuteNonQuery();
// Eliminiamo qualsiasi valore dal database
cmd.CommandText = "DELETE FROM Prova";
cmd.ExecuteNonQuery();
// Inseriamo i nuovi valori casuali nella tabella. Notate
// l'utilizzo del parametro @Testo all'interno della query.
// Visto che dobbiamo inserire 5 righe nella tabella utilizzando
// sempre la stessa query (fatta eccezione per il valore
// da inserire), l'uso di un parametro permette di mantenere
// il codice più leggibile e di aumentare notevolmente le prestazioni.
cmd.CommandText = "INSERT INTO Prova (Valore) VALUES (@Valore)";
// Creiamo il parametro
cmd.Parameters.Add("@Valore", DbType.Int32);
// Per generare i valori casuali
Random r = new Random();
// Inseriamo i valori
for (Int16 i = 0; i < 5; i++)
{
// Specifichiamo il valore casuale per il parametro
cmd.Parameters["@Valore"].Value = r.Next(1, 100);
cmd.ExecuteNonQuery();
}
// Creiamo un nuovo oggetto SQLiteCommand per leggere
// dal database.
cmd = new SQLiteCommand(conn);
cmd.CommandText = "SELECT * FROM Prova";
// Utilizziamo l'oggetto SQLiteDataReader per una lettura
// veloce dei dati.
SQLiteDataReader reader = cmd.ExecuteReader();
if (reader.HasRows)
{
// Leggiamo i dati e stampiamoli a video
while (reader.Read())
{
Response.Write("ID: " + reader.GetInt32(0) + "<br />");
Response.Write("Valore: " + reader.GetInt32(1) + "<hr />");
}
reader.Close();
}
else
{
// Non sono presenti dati, generiamo un errore
throw new Exception("Nessun dato trovato.");
}
}
catch (SQLiteException ex)
{
// Errore specifico di SQLite
Response.Write("Errore specifico di SQLite! <br />" + ex.ToString());
}
catch (Exception ex)
{
// Errore generico
Response.Write("Errore generico! <br />" + ex.ToString());
}
finally
{
// Chiudiamo la connessione al database
// Questo codice verrà eseguito comunque anche in caso di errore
// evitando quindi di lasciare connessioni aperte.
conn.Close();
}
}
Abbiamo commentato molto il codice per spiegare ogni passaggio, ma il tutto è molto simile a quanto normalmente facciamo con qualunque connettore.
Conclusioni
Abbiamo iniziato a conoscere SQLite, un motore di database molto leggero e utile per immagazzinare quantità di dati relativamente piccole, di cui sono stati evidenziati i principali pregi e difetti. È stata poi fornita una panoramica sui programmi che possiamo utilizzare per gestire i nostri archivi. In conclusione è stato introdotto il provider System.Data.SQLite per utilizzare SQLite con il .Net Framework.
E adesso? In un prossimo articolo vedremo come utilizzare SQLite per la gestione dell'autenticazione basata sul Membership provider di ASP.Net. Nel frattempo vi rimando al sito ufficiale di System.Data.SQLite, dove si possono trovare diversi tutorial all'interno del forum, e all'articolo di Peter A. Bromberg "Experimenting with SQLite and the SQLite.NET Provider" , che, nonostante non sia più molto recente, rimane sicuramente un ottimo punto di partenza per una comprensione migliore del provider ADO.Net per SQLite.