giovedì 15 maggio 2008

Utilizzo di SQLite su Visual Studio 2005 - 2008

SQLite è uno dei DataBase che più agevolmente si presta a diventare un valido sostituto di Access.
Come database embedded è leggerissimo, non ha bisogno di nessuna installazione se non quella del provider ADO.NET System.Data.SQLite.

Anche se non obbligatorio, è' opportuno scaricare il database e i tools dal sito di SQLite soprattutto sqlite3.exe che fornisce una console "linea comandi" di gestione al database, una vera manna per gli smanettoni.

Oltre a fornire l'interfaccia / provider ADO.NET di SQLite è possibile "disegnare" la propria base dati e, all'occasione poter creare il database.

Il problema dell'interfaccia ide è che si tratta di un addon, sulla versione Express non è prevista, nel designer di connessioni e dataset altri database che non siano Access o Sql Express entrambi gestiti in locale.

Per poter aggirare questa limitazione i creatori del driver hanno pensato bene di permettere di "sganciare" da express il provider di Access e sostituire provvisoriamente il proprio a questo.

L'installazione aggiunge nel menù un semplice programma che su Visual Express provvede a sganciare / riagganciare access tra i provider.

Con esclusione della versione Express 2008 su tutte le altre versioni di Visual Studio 2005 e 2008 è possibile utilizzare il wizard per la definizione del dataset tipizzato, della connessione ai dati ed eventualmente creare anche il database.

Su Express 2008 c'è un bug già riconosciuto che impedisce l'utilizzo del wizard e del designer, il collegamento "a mano" con la definizione da codice è sempre possibile, come è possibile in tutte le altri progetti basati sul Framework 2 o successivi.

Nei casi in cui non sia possibile geneare un nuovo database tramite l'ide è possibile utilizzare il programma console sqlite3.exe.

Un esempio di utilizzo del programma a linea comandi:

D:\sqlite>sqlite3.exe dbprova.db3
sqlite> create table tableprova (id integer primary key autoincrement, descrizione varchar(50), valore decimal(10,3));

Per "solidarietà" con la versione express non utilizzo la definizione del dataset tipizzato / tableadapter.
Tra l'altro .. sono andato a "sbirciare" sul dataset tipizzato / tableadapter di prova generato da visual express 2005, beh il codice è comunque incompleto.

Se può essere d'aiuto la gestione del dataset può essere fatta con un database di access e poi si può intervenire nella classe generata dal wizard con una nostra "partial class" che dall'interno riconfiguri tutti gli elementi (command, connection, ecc) facendoli puntare a SQLite.

Proseguiamo con l'aggiungere un dataset non tipizzato al progetto.

Aggiungiamo un DataTable "tableprova" al progetto e i 3 campi

Campo Tipo Note
id Int32 PK, autonumber (-1 sul seed e sullo start)
descrizione string  
valore decimal 12 interi,3 decimali

Completata questa fase è possibile aggiungere alla form selezionandoli dalla casella degi strumenti il controllo BindingSource e il controllo BindingNavigator .

Agendo sulla prprietà dei controlli colleghiamo BindingNavigator a BindingSource e BindingSource al DataSet il bindinsource collegato al dataset/tableprova.

Ora inizia la parte difficile, dobbiamo sostituire quanto fa il designer che genera il TableAdapter  un oggetto che è costruito ad hoc dal wizard, al suo posto utilizzeremo un normale DataAdapter.

A livello di classe definiamo un oggetto di tipo DataAdapter 

C#
SQLiteDataAdapter daTableProva;
// Definiamo anche la stringa di connessione

string Connessione = Properties.Settings.Default.Connessione;

VB.NET
Dim daTableProva As SQLiteDataAdapter
'Definiamo anche la stringa di connessione
Dim Connessione As String = My.Settings.Connessione

Per la configurazione del SQLiteDataAdapter ho introdotto un metodo apposito "Inizializza".

C#

void Inizializza()
{
   SQLiteConnection cn = new SQLiteConnection(Connessione);
   daTableProva = new SQLiteDataAdapter("SELECT * FROM tableprova", cn);
   System.Data.Common.DataTableMapping tableMapping = new System.Data.Common.DataTableMapping();
   tableMapping.SourceTable = "Table";
   tableMapping.DataSetTable = "tableprova";
   tableMapping.ColumnMappings.Add("id", "id");
   tableMapping.ColumnMappings.Add("descrizione", "descrizione");
   tableMapping.ColumnMappings.Add("valore", "valore");
   daTableProva.TableMappings.Add(tableMapping);
   SQLiteCommandBuilder cmb = new SQLiteCommandBuilder(daTableProva);
   daTableProva.InsertCommand = cmb.GetInsertCommand();
   daTableProva.UpdateCommand = cmb.GetUpdateCommand();
   daTableProva.DeleteCommand = cmb.GetDeleteCommand();

}

VB.NET

   Private Sub Inizializza()
        Dim cn As New SQLiteConnection(Connessione)
        daTableProva = New SQLiteDataAdapter("SELECT * FROM tableprova", cn)
        Dim tableMapping As New System.Data.Common.DataTableMapping()
        tableMapping.SourceTable = "Table"
        tableMapping.DataSetTable = "tableprova"
        tableMapping.ColumnMappings.Add("id", "id")
        tableMapping.ColumnMappings.Add("descrizione", "descrizione")
        tableMapping.ColumnMappings.Add("valore", "valore")
        daTableProva.TableMappings.Add(tableMapping)
        Dim cmb As New SQLiteCommandBuilder(daTableProva)
        daTableProva.InsertCommand = cmb.GetInsertCommand()
        daTableProva.UpdateCommand = cmb.GetUpdateCommand()
        daTableProva.DeleteCommand = cmb.GetDeleteCommand()
    End Sub

Problema del recupero dell'ultimo ID nel caso di campi chiave autonumber.
Il comando sql per generare un campo autonumber durante la creazione di una tabella SQLite è:
CREATE TABLE mytable (id integer primary key autonumber, ..altri campi);
Su SQLite esiste una funzione last_insert_rowid() che restituisce l'ultimo id attribuito.

Per recuperarlo nel nostro dataset è necessario "abbonarsi" all'evento RowUpdated di SQLiteDataAdapter.

C#
daTableProva.RowUpdated += new EventHandler<System.Data.Common.RowUpdatedEventArgs>(daTableProva_RowUpdated);
...

void daTableProva_RowUpdated(object sender, System.Data.Common.RowUpdatedEventArgs e)

{
    // recupero dell'ultimo id attribuito automaticamente negli
    // inserimenti
    if (e.Row.RowState == DataRowState.Added)
    {
       SQLiteCommand cm = new SQLiteCommand("SELECT last_insert_rowid() As ID", daTableProva.SelectCommand.Connection);
       SQLiteDataReader dr = cm.ExecuteReader();
       int k = 0;
       if (dr.Read())
         k = (int)((Int64)dr[0]);
       e.Row["id"] = k;
    }
}



VB.NET
AddHandler daTableProva.RowUpdated, AddressOf daTableProva_RowUpdated

...

Private Sub daTableProva_RowUpdated(ByVal sender As Object, ByVal e As System.Data.Common.RowUpdatedEventArgs)
    ' recupero dell'ultimo id attribuito automaticamente negli
    ' inserimenti
    If e.Row.RowState = DataRowState.Added Then
       Dim cm As New SQLiteCommand("SELECT last_insert_rowid() As ID", daTableProva.SelectCommand.Connection)
       Dim dr As SQLiteDataReader = cm.ExecuteReader()
       Dim k As Integer = 0
       If dr.Read() Then
          k = CInt(DirectCast(dr(0), Int64))
       End If
       e.Row("id") = k
    End If
End Sub

Progetto di esempio in C# realizzato con Visual Studio 2008
Progetto di esempio VB.NET realizzato con Visual Studio 2008

Powerered with Window Live Writer

3 commenti:

Anonimo ha detto...

Ciao


proprio oggi ho scaricato Sqlite, e da un po' sto' smanettando con visual C# 2008 express.

Come Sql server CE, è possibile anche con Sqlite 'inglobare' il tutto nell'eseguibile?.

Non ho trovato ancora un tutorial chiaro...

grazie e complimenti per l'ottimo sito.

Luciano Bastianello ha detto...

Ciao
E' ovvio che per "database embedded" non si indica database inserito e gestito come risorsa incorporata nell'eseguibile.
Il termine sta ad indicare che non c'è bisogno di utilizzare un server di database per la connessione e che l'accesso è diretto al file del database.
Per quanto riguarda invece la distribuzione, ok puoi mettere il database come risorsa incorporata per la generazione di un nuovo database da utilizzare.

stefano ha detto...

Sto sviluppando un applicazione in c# che utilizza un db SQLite, per un palamare.
Per fare un semplice test, ho provato a creare una piccola classe che caricava gli elementi di una listBox ma in run-time mi genera: "Eccezione first-chance di tipo 'System.MissingMethodException' in System.Data.SQLite.dll " nostro progetto c# per smart Device con VS2008
tutto con .net 2.0
Ecco qui il codice:
using System;

using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.Data.SQLite;

using System.Runtime.InteropServices;
using System.Collections;



namespace provaSQliteDevice
{
public partial class Form1 : Form
{
System.Data.SQLite.SQLiteConnection sql_con;
SQLiteCommand sql_cmd;
public Form1()
{
InitializeComponent();
LoadData();
}

public void LoadData()
{

try
{

sql_con = new SQLiteConnection("data source=marco.db");
sql_cmd = new SQLiteCommand();
listBox1.Items.Add(sql_cmd);
sql_cmd.CommandText = "select desc from sin";
listBox1.Items.Add(sql_con.State);
sql_cmd.Connection = sql_con;
listBox1.Items.Add("3");
sql_con.Open();
listBox1.Items.Add("4");
// sql_cmd = sql_con.CreateCommand();
SQLiteDataReader reader = sql_cmd.ExecuteReader();
listBox1.Items.Add("5");
while (reader.Read())
{
listBox1.Items.Add(reader.GetString(0));
}
reader.Close();
sql_con.Close();
}
catch (Exception e)
{
Console.WriteLine("Errore Connessione d:"+e.Message);
}

}


}
}

//FINE

Da non osservare i vari add che sono utilizzati per cercare gli errori
e penso che l'errore sia in sql_con.Open