LINQ to SQL - Parte I

LINQ rappresenta una delle novità più importanti per lo sviluppo nella prossima release di Visual Studio.

LINQ è una serie di estensioni al linguaggio, sia per C# che VB.NET, che ci permettono di utilizzare degli operatori "alla SQL" per lavorare su diverse sorgenti dati.

Questa è la terza parte di una serie di post. Negli altri post ho affrontato i segueti argomenti:

Ora vi racconto l'implementazione di Linq più interessante: LINQ to SQL. LINQ to SQL ed il designer di Visual Studio 2008 sono pensati per reallizzare applicazioni che usano SQL Server come database. Il designer genera molto del codice che fare a mano sarebbe tedioso, ma al contempo ci lascia la possibilità di intervenire con soluzioni più mirate, dandoci la possibilità di cambiare o estendere il codice generato.

LINQ to SQL

Linq to SQL vi permette di utilizzare la sintassi vista in precedenza per interrogare SQL Server, la query espressa verrà poi convertita in una chiamata ADO.NET. Visual Studio 2008 mette a disposizione nell' IDE  un designer che ci aiuta, a partire da un database SQL Server esistente, a mappare le tabelle alle classi che useremo nella nostra applicazione.

Configuriamo Northwind

Negli esempi seguenti userò come database  il famosissimo Northwind, che potete scaricare qui.  Come DBMS ho usato SQL Server 2005 Express Edition SP2, che potete scaricare gratuitamente; allo stesso link potete trovare anche la SQL Server Management Studio Express, l'applicazione client per l'amminstrazione.

Aggiungiamo il database Northwind alla lista di quelli gestiti da SQL Server Express. Apriamo la console di SQL Server Management Studio Express.

image

Tasto destro su Databases, quindi Attach, appare la schermata sopra in figura, dove possiamo aggiungere il file Northwnd.MDF: premere il tasto Add ... e quindi scegliere la directory in cui è copiato il file.

Alla fine il nostro database verrà aggiunto alla lista di quelli gestiti. Chiudiamo il Management Studio.

Creiamo l'applicazione di test con Visual Studio 2008 ed usiamo il designer

Creiamo ora una console application con Visual Studio 2008, in VB.NET o in C#.  Aggiungiamo alla lista delle connessioni in Visual Studio quella al database Northwind che verrà visualizzata nel Server Explorer (per visualizzarlo View -> Server Explorer). Quindi premendo il bottone "connect to database" possiamo visualizzare direttamente da Visual Studio il nostro database e avere accesso agli elementi che lo costituiscono: tabelle, store procedues, viste etc.

image

Per accedere al Designer che ci consente di effettuare il mappaggio delle tabelle alle classi aggiungiamo un nuovo item, tra la lista dei templates troviamo LINQ to SQL Classes come mostrato in figura.

linq2_thumb[4]

Una volta rinominato il file in Northwnd.dbml e aggiunto al progetto, l' IDE di VS 2008 ci dà la possibilità di fare il drag-and-drop delle tabelle del database al fine di costruire le classi ad esse associate. Così in figura ho fatto il drag-and-drop delle tabelle Products, Categories, Orders, Customers che hanno generato le relative classi Product, Category, Order e Customer.

image

Se diamo un'occhiata ai file del nostro progetto vedrete che sono stati aggiunti tre file, se aprite il file Northwind.designer.cs potete notare che cosa ha creato il designer di Visual Studio.

Vi faccio notare che è stata generata una classe NorthwindDataContext che deriva dalla classe DataContex . Come vedrete a breve la query che faremo sul database sarà sullo stile di quelle viste con Linq To XML e Linq to Objects. La classe DataContext ha la funzione di "mascherare" la connessione verso il database e di rendere possibili le operazioni di aggiornamento verso il database. Ogni tabella del nostro database sarà accedibile come proprietà pubblica di questa classe e sarà, a sua volta, una classe di tipo Table. Ad esempio ci sarà la possibilità di accedere alla classe Products che è di tipo Table<Product> . La classe Product viene descritta nel prossimo paragrafo. In breve quindi la classe DataContext è l'entry point per poter utilizzare Linq To SQL.

Come funziona il mapping del designer tra il database e le classi ?

Sempre all'interno del file generato trovate una serie di attributi che specificano il mappaggio con il database, le tabelle e le classi.  La classe NorthwindDataContext viene mappato sul database Northwind. 

image

La classe Product viene  mappata sulla tabella Products.

image

All'interno della classe Product la proprietà ProductID viene mappata sulla corrispondente colonna della tabella del db e vengono inoltre indicate altre caratteristiche: ad esempio che è un chiave primaria ed il tipo che la rappresenta nello schema del db. L'attributo AutoSync stabilisce che relazione di sincronizzazione ci deve essere tra la proprietà dell'oggetto e la row del db. Nel nostro caso la proprietà dell'oggetto viene valorizzato quando un prodotto viene effettivamente inserito nel database, notate che sul database la colonna è una IDENTITY. Qui il dettaglio sull'attributo Column.

image

Il mappaggio che abbiamo visto si basa sul fatto che le classi della nostra applicazione ad esempio Products, che useremo con LINQ, sono mappate tramite una serie di attributi al nostro database. Questa non è l'unica soluzione adottabile: è infatti possibile disaccopiare la classe dall'attributo che la mappa sul database e usare un file XML per effettuare tale mappaggio, ma questo sarà un argomento di un altro post ...Per chi vuole subito informazioni su come costruire il proprio modello ad oggetti con Linq To SQL, segua il link.

Una semplice query ...

Vogliamo ora ottenere il nome dei prodotti della categoria "Beverages", scriviamo quindi:

C#:

image

Cosa c'è da notare?:

Ho aggiunto il namespace a System.Data.Linq; per utilizzare le estensioni al linguaggio per Linq To SQL

Notatate che p è di tipo Products, cioè una classe con un tipo ben preciso, quello che abbiamo visto nei paragrafi sul mappaggio, inoltre da p posso accedere non solo alla proprietà Category, ma anche al nome della categoria stessa! Se andiamo a vedere la tabella Products sul database ci accorgiamo che contiene solo la colonna CategoryID che in relazione con la chiave primaria della tabella Categories. E' proprio in questa tabella che si trova la colonna CategoryName.

image

L'output dell'applicazione è il seguente: la lista dei prodotti della categoria cercata:

image

in VB.NET:

image

la cui logica è equivalente a quella vista in precedenza.

Che codice SQL viene generato e quando ?

Penso di prevedere una vostra domanda, anzi due.

Per vedere il codice generato, inseriamo la seguente riga di codice e rieseguiamo l'applicazione:

image

Come vediamo ora nella console application viene mostrato il codice SQL che viene eseguito in SQL Server:

image

Come vedete viene eseguita una prima chiamata SELECT in LEFT OUTER JOIN tra la tabella Products e la tabella Categories, questo perchè è solo in quest'ultima tabella che troviamo il nome della categoria. Ricordate quanto è stato semplice con Linq scrivere p.Category.CategoryName, senza preoccuparci del Join tra le tabelle. Il modello ad oggetti offerto forniva già la possibilità di navigare in relazioni 1 a molti. La seconda query è stata generata perchè abbiamo voluto avere il nome della categoria nel ciclo for each. Operazione che certamente potevamo evitare, almeno in questo semplice esempio.  

Quando viene eseguita la query sul database? Se mettiamo un break-point (F9) sul cliclo foreach nell'esempio di codice, potremmo essere sorpresi di una cosa. Se guardiamo infatti la console application non vediamo ancora visualizzato il codice di esecuzione su SQL. Questo perchè non è ancora stato eseguito! Infatti nostante ci potremmo aspettare che il codice Linq viene immediatamente tradotto in codice SQL standard, non è così. Premiamo F10 un po' divolte e ci accorgiamo che il codice viene eseguito quando serve, cioè durante l'enumerazione del ciclo. Linq utilizza un modello di esecuzione differtita (deferred execution). Questo perchè LINQ analizza le operazioni che fino a quel momento sono state eseguite prima di comporre la query. In certi scenari questo comportamento è sicuramente desiderabile, in altri sarebbe desiderabile l'opposto. E quindi possibile eseguire la query una volta e riutilizzarla successivamente. Per fare questo è sufficiente convertire il risultato della query in una collezione standard usando gli operatori ToList() o ToArray() .

In breve ...

In questo primo post abbiamo visto come, in pochi semplici passi, possiamo trarre beneficio da Linq to SQL e dal O/R (Object Relational) Designer di VS 2008. L'uso di quest'ultimo è sicuramente di aiuto nello sviluppo di applicazioni RAD e in alcuni scenari non riechiede di scrivere codice ulteriore a quello già generato. Tuttavia nei prossimi post vedremo come personalizzare il codice gerato per adattarlo agli scenari di altri tipi di applicazioni.

Linq to SQL ci consente di accedere ai dati in modo diverso da quello fatto tradizionalmente con ADO.NET, realizzando uno strato di software per l'accesso ai dati, che ci consente di mappare uno a uno classi e tabelle di un database SQL e di lavorare a livello applicativo concentrandoci solo sugli oggetti senza preoccuparci troppo della struttura del nostro database SQL Server.

Nelle prossime "puntate":

Nei prossimi post vedremo come fare operazioni di scrittura sul database e come estendere e modificare il codice generato in automatico.

In questo articolo LINQ to SQL viene presentato in modo chiaro e completo.

LINQ to SQL: .NET Language-Integrated Query for Relational Data (articolo MSDN in inglese)

Percorso:

Per chi non ha seguito passo-passo consiglio il seguente percorso con laboratori:

  1. Scaricare la beta 2 di Visual Studio 2008
  2. Laboratori di Linq in C#
  3. Laboratori di Linq in VB.NET

Ciao e alla prossima.

-Pietro