LinQ (Language Integrated Query) è una delle più grandi novità di .Net 3.5. Si tratta di un nuovo sistema per l'esecuzione di query SQL integrate direttamente nel codice C# o VB.Net.
Facciamo una brevissima introduzione ai Database (poi però approfondite per conto vostro).
Un database è un file dati organizzato opportunamente. Un DBMS (database management system) è un software in grado di aprire questi file e gestirli in lettura e scrittura. Esistono moltissimi tipi di DataBase (Oracle, SQL Server, MySQL, Access), ognuno con le proprie caratteristiche. Per dare la massima libertà di interrogazione è stato inventato un linguaggio di "programmazione" per leggere e scrivere dati nei database: SQL.
Il linguaggio SQL è uno standard comunque a tutti i database (salvo set di funzioni aggiunte specifiche che possono avere i vari DB) ed il DBMS non fa altro che prendere in input tramite varie librerie di comunicazione questo codice, eseguirlo e restituire il risultato. Questa si chiama Query. Ogni DB però richiedeva l'utilizzo delle sue librerie di comunicazione.
Nel corso degli anni una delle preoccupazioni è stata quella di unificare il più possibile la gestione di DataBase diversi in modo da essere gestiti da un set minimo di librerie. Da qui sono nati ad esempio OleDB, ODBC e Ado. Ogni produttore fornisce semplicemente i driver utilizzabili da questi sistemi ed il programmatore si svincolava dal dover utilizzare librerie specifiche per ogni tipo di DB.
Il sistema query rimane comunque qualcosa di separato di fatto dai linguaggi di programmazione. Chi scrive un programma che usa DataBase scrive le sue query e le invia come stringhe tramite le varie classi presenti (in .Net ad esempio c'è SQLCommand).
Tuttavia non sapremo della correttezza della query fino alla sua effettiva esecuzione, nè se i dati che ci arriveranno saranno effettivamente quelli attesi.
I dati che poi ci arriveranno dovranno essere inseriti in nostre classi e strutture che dovremo gestire manualmente.
Prendiamo ad esempio due tabelle
Studente
Esame
- Nome Esame
- Matricola
- Voto
Vogliamo l'elenco degli esami superati da tale studente. Si ottiene banalmente unendo le due tabelle tramite la matricola. Ora però cosa vogliamo mostrare?
Solo il nome dell'esame? Anche il voto? O tutti i dati? Se vogliamo usare una struttura a classi dovremo quindi creare classi per ogni tipo dati che vogliamo usare, cambiandolo in caso di modifiche.
L'approccio che Microsoft invece vuole fornire tramite LinQ è di un sistema che si inserisca direttamente nel codice e ci permetta di sapere direttamente ciò che ci arriva e se abbiamo fatto tutto correttamente. LinQ è una sintassi molto simile ai classici blocchi if, while o for e che usa le classi anonime per definire nuovi tipi.
Un altro vantaggio immenso di LinQ è la possibilità di svincolarsi non solo dal DataBase, ma dal tipo di dati che ci arrivi. In LinQ potremo gestire allo stesso modo DataBase, XML e addirittura liste ed array (anche mescolandole tra loro).
In questa primo tutorial introdurrò LinQ applicandolo a liste. Nei successivi mostrerò come utilizzare un DataBase e come sfruttura le utility inserite in Visual Studio 2008.
Tipi Anonimi
Il framework .Net 3.5 permette di definire classi al momento della compilazione ed utilizzabili a livello di funzione. Facciamo un esempio
var utente =new {Nome="Roberto", Sito = "www.NotJustCode.it"};
MessageBox.Show(utente.Nome);
var è una parola per indicare che il tipo viene creato nel momento della compilazione. La classe utente diventerà quindi una istanza di un tipo simile a questo
public class MioTipo
{
String Nome;
String Sito;
}
L'editor comunque aiuterà tramite l'intellisense riconoscendo che utente effettivamente contiene i campi Nome e Sito, ed il loro tipo che sarà riconosciuto automaticamente.
Oltre che per LinQ i tipi anonomi sono utili per definire classi che devono essere usate solamente in un metodo e renderebbe solo lungo e pesante dichiarare in un file di codice.
LinQ
Mostrerò un pò di costrutti fondamentali.
L'esempio che ho scritto apre una directory a vostra scelta, prende le informazioni su i file ed esegue delle query su questa collezione per filtrare i dati. La classe è la seguente
public class FileDescription
{
private string nome;
private DateTime creato;
private DateTime modificato;
private long dimensione;
private string estensione;
private FileAttributes attributo;
public string Nome
{
get { return nome; }
set { nome = value; }
}
public DateTime Creato
{
get { return creato; }
set { creato = value; }
}
public DateTime Modificato
{
get { return modificato; }
set { modificato = value; }
}
public long Dimensione
{
get { return dimensione; }
set { dimensione = value; }
}
public string Estensione
{
get { return estensione; }
set { estensione = value; }
}
public FileAttributes Attributo
{
get { return attributo; }
set { attributo = value; }
}
}
E li carichiamo in questa maniera
string[] files = Directory.GetFiles(directory);
List fileList= new List();
foreach (string s in files)
{
FileInfo info = new FileInfo(s);
FileDescription desc = new FileDescription
{
Nome = info.Name, Modificato = info.LastWriteTime, Creato = info.CreationTime,
Dimensione = info.Length, Estensione = info.Extension, Attributo = info.Attributes
};
fileList.Add(desc);
}
Ora fileList è una Lista di classi FileDescription.
Selezione
La prima operazione da fare è una Select, la selezione di tutti gli elementi
var data = from f in fileList orderby f.Nome select f;
l'oggetto data sarà una lista LinQ contenente tutte le classi. Questo oggetto potrà essere usata in un ciclo for, come DataSource per un controllo oppure può essere usato in un'altra query LinQ.
L'oggetto data infatti contiene tutta una serie di metodi per filtrare ulteriormente i dati.
Ad esempio
data.ElementAt(N); ci dà l'elemento in posizione N
oppure
data.Max(new Func(Funzione));
public decimal Funzione(FileDescription desc)
{
return desc.Dimensione;
}
Max ci restituisce l'elemento che ha uno degli elementi come valore massimo. Questa struttura permette di creare un delegato a cui vengono inviati tutti gli elementi della lista e ci viene chiesto di restituire l'elemento che vogliamo sia usato come Max (ma possiamo anche inventare nostre formule).
Gli stessi campi possono essere usati anche nella query stessa
int data = (from f in fileList select f).Count();
notare che sapendo già che Count è un intero possiamo utilizzare direttamente int (ma sarebbe cambiato poco, come var sarebbe stato comunque un intero)
Se vogliamo selezionare solo alcuni campi
var data = from f in fileList
select new { Nome = f.Nome, Dimensione = f.Dimensione, DimensioneKb = f.Dimensione / 1024, Creazione = f.Creato.ToString("dd-MM-yyyy") };
data contiene una classe anonima con i campi Nome, Dimensione, DimensioneKb e Creazione.
Comparazione
La sintassi where permette di filtrare i campi tramite operazioni di comparazione
var data = from f in fileList where (f.Dimensione > 1000) select f;
Questa query restituisce gli elementi con Dimensione maggiore di 1000 byte. La sintassi where funziona identicamente ad una clausola if.
Ordinamento
La sintassi orderby ordina gli elementi per uno o più campi dei campi
var data = from f in fileList orderby f.Nome select f;
var data = from f in fileList orderby f.Nome, f.Dimensione select f;
Potete usare descending ed ascending per selezionare l'ordine
var data = from f in fileList orderby f.Nome descending select f;
Intersezione
Anche in LinQ si possono ovviamente fare operazioni di Join (intersezione tra tabelle)
List estensioniValide = new List();
estensioniValide.Add(".bmp");
estensioniValide.Add(".jpg");
estensioniValide.Add(".gif");
estensioniValide.Add(".tga");
estensioniValide.Add(".png");
var data = from f in fileList join e in estensioniValide on f.Estensione.ToLower() equals e select f;
In questa query vengono selezionati i file con estensione presente nella lista scritta sopra.
Raggruppamento
Per raggruppare gli elementi si utilizza questa forma
var data = from f in fileList
group fileList by f.Estensione into g
select new { Numero = g.ToArray().Count(), Estensione = g.Key };
O nel caso di raggruppamento per più elementi
var data = from f in fileList
group fileList by new { f.Estensione, f.Attributo } into g
select new { Numero = g.ToArray().Count(), Estensione = g.Key.Estensione, Attributo = g.Key.Attributo };
L'istruzione group suddivide i dati in gruppi che vengono inseriti nella variabile che abbiamo chiamato g.
Con questa vengono in pratica creati dei gruppi di elementi aventi Estensione ed Attributo uguali. Quello che viene mostrata è la dimensione del gruppo e gli elementi sui cui si ha raggruppato.
SubQuery
E' possibile creare query nidificate attraverso la clausula let che permette il salvataggio di dati temporanei.
var data = from f in fileList let m = ((from f2 in fileList select f2.Dimensione).Max()) where f.Dimensione == m select f;
Nella variabile m ho inserito l'elemento di dimensione massima della lista. Con questo valore ho fatto una comparazione nella query più esterna prendendo solo l'elemento di dimensione massima. Questa query è equivalente a selezionare il massimo salvandolo in una variabile per poi usare questa per fare una normale select.
Con questo tutorial spero di avervi fatto dare una prima occhiata a LinQ. Nei prossimi tutorial vedremo come utilizzare un vero DataBase e come fare operazione di insert ed update.
Vi lascio ad un semplice demo. Aprite una directory sul vostro computer e testate le varie chiamate che filtreranno le informazioni in base a varie query.
Demo