Linq to SQL è sicuramente la più importante delle implementazioni di Linq inserite nella nuova versione del .NET Framework; rappresenta uno strumento completo e semplice, per lavorare con database SQL Server. Possiamo operare sulla base di dati utilizzando gli oggetti del database sotto forma di classi.
Linq to SQL è un vero e proprio ORM, che permette quindi di mappare gli oggetti di un database SQL Server in classi .NET, facilitando anche la costruzione del domain model di applicazioni Web e client.
Linq to SQL si rivela quindi lo strumento ideale per la creazione dell'architettura di base di applicazioni .NET basate sulla versione 3.5 del .NET Framework. Questo perché esso si occupa sia del modello ad oggetti dell'applicazione, ovvero dell'interscambio di dati tra i livelli concettuali in cui sono solitamente divise le applicazioni, e dei meccanismi di persistenza delle informazioni.
A supporto di questa nuova tecnologia, Visual Studio 2008 mette in campo l'Object-Relational Designer, che consente di effettuare il mapping tra applicazione e database, tramite semplici operazioni visuali come il drag & drop. Gli schemi realizzati sono poi memorizzati con estensione .dbml
.
La struttura di Linq to SQL
Come abbiamo già accennato Linq to SQL mette in campo due tipologie principali di funzionalità:
- Ciò che permette la definizione di un modello ad oggetti basato sul mapping delle tabelle di un database SQL Server
- Un vero e proprio runtime per la gestione dei meccanismi di aggiornamento, persistenza, gestione delle performance e della cache
Il mapping offerto da Linq to SQL, presenta delle relazioni 1:1 tra tabelle della base di dati e classi che andranno poi a far parte del domain model dell'applicazione. In realtà il mapping "si avvicina molto" al pattern 1 a 1, poiché possiamo arricchirlo con associazioni, collezioni e meccanismi di ereditarietà basati sulla strategia a Single Table Hierarchy.
Le classi generate da questa operazione di mapping, sono le cosiddette entity, che possono essere descritte appunto come entità logiche che rappresentano le informazioni che dovranno poi essere gestite dai vari livelli concettuali in cui sono divise le applicazioni multilivello.
Queste entità sono gestite dal runtime di Linq to SQL, che invece si occupa della connessione fisica tra applicazione e base di dati. Il che ci permette di effettuare operazioni sul database, secondo il modello di programmazione ad oggetti, senza dover specificare alcuna riga di codice T-SQL, in quanto è proprio il runtime stesso che provvede alla conversione delle nostre operazioni in costrutti sintattici propri del linguaggio con cui solitamente accediamo alle informazioni poste nella base di dati.
Per favorire questo tipo di funzionalità, Linq to SQL si basa sul DataContext
: una classe che fornisce il punto di incontro tra entità logiche (mapping) e database (entità dell'RDBMS).
Il DataContext
rappresenta il concetto di "contesto di persistenza", concetto che sta alla base di ogni ORM che si rispetti; esso si occupa infatti sia delle connessioni fisiche alla base di dati, facendo da wrapper per un'istanza di una classe che implementa l'interfaccia IDBConnection
e gestendo l'apertura e la chiusura della connessione, sia di tutti i meccanismi di persistenza delle entity.
Proprio come un oggetto di tipo "connection" va quindi utilizzato con parsimonia, tipicamente all'interno di blocchi "using".
Quando definiamo un mapping con l'O-R Designer di Visual Studio 2008, otteniamo sia un file di mapping (.dbml), sia la creazione di una classe che eredita direttamente da DataContext
(nel namespace System.Data.Linq
), con proprietà tipizzate relative alle tabelle mappate e metodi relativi ad ogni singola stored procedure definita.
Esempio d'utilizzo: mapping del database Northwind
Come primo esempio di utilizzo di Linq to SQL, vediamo come effettuare il mapping del famoso database Northwind di SQL Server e come utilizzare le entity create all'interno di un'applicazione Web ASP.NET.
Nota: per il corretto funzionamento del codice allegato e per comprendere a fondo il codice presente in questo articolo, occorre scaricare il database Northwind dal download center di Microsoft ed installarlo in locale.
Inseriamo un nuovo oggetto di tipo "Linq to SQL classes" nella cartella App_Code
dell'applicazione Web. Visual Studio 2008 apre automaticamente il designer object-relational per la gestione visuale del mapping. In parallelo crea la classe tipizzata che eredita da DataContext
.
Possiamo prelevare le tabelle che ci interessano direttamente dal "Server Explorer" trascinandole sulla parte sinistra del designer. Per le stored procedures possiamo fare altrettanto trascinandole sulla parte destra. Così facendo abbiamo creato il domain model della nostra applicazione Web e il contesto di persistenza.
A titolo d'esempio, abbiamo inserito il mapping solo per le tabelle Customers, Categories, Orders, Order_Detail e Products; questi oggetti saranno rappresentati all'interno dell'applicazione con classi omonime, che presentano una proprietà per ogni colonna definita nello schema della tabella mappata.
Il contesto di persistenza creato in automatico è rappresentato dalla classe NorthwindDataContext
(nome generato in automatico a partire dal nome del file .dbml, ma che possiamo modificare a piacimento all'interno della finestra delle proprietà del file .dbml).
La differenza principale tra un'istanza normale della classe DataContext
e una di NorthwindDataContext
è che la seconda presenta già delle proprietà tipizzate, a rappresentare ogni tabella inserita all'interno del mapping (avremo quindi le proprietà Customers
, Products
e così via), mentre tramite la prima, dobbiamo ottenere il riferimento al contenuto di una tabella attraverso il metodo GetTable
.
Passando per il contesto di persistenza, possiamo effettuare delle operazioni sulla base di dati attraverso la nuova sintassi Linq per l'interrogazione di collezioni di oggetti in memoria.
Selezione
L'operazione di selezione è sicuramente la più intuitiva, soprattutto per chi ha già un po' di basi sull'utilizzo di Linq per la selezione di informazioni.
Dopo avere creato una nuova istanza del nostro NorthwindDataContext
, possiamo scrivere una query di questo genere, senza doverci preoccupare di aprire/chiudere la connessione o di formulare la sintassi SQL corretta per selezionare le informazioni desiderate.
Esempio di selezione (C#)
NorthwindDataContext northwind = new NorthwindDataContext(); using (northwind) { var customersFromLondon = from c in northwind.Customers where c.City == "London" select c; GridView1.DataSource = customersFromLondon; GridView1.DataBind(); }
Esempio di selezione (VB.NET)
Dim northwind As New NorthwindDataContext() Using (northwind) Dim customersFromLondon = From c In northwind.Customers _ Where c.City = "London" _ Select c GridView1.DataSource = customersFromLondon GridView1.DataBind() End Using
La query Linq sulla tabella Customers
ritorna una collezione di tipo IEnumerable
che possiamo gestire a nostro piacimento all'interno dell'applicazione (nell'esempio è stata passata come fonte di dati ad una GridView
).
Attraverso la sintassi del nuovo linguaggio di interrogazione integrato, possiamo raggiungere un notevole livello di complessità per le nostre query di selezione, senza dover scrivere alcuna riga di codice SQL.
Inserimento
Per quanto riguarda l'inserimento invece, abbiamo a che fare direttamente con le entity create dal mapping uno a uno con le tabelle della base di dati.
Per inserire un nuovo prodotto infatti, dobbiamo prima creare una nuova istanza della classe Product
, inserire le informazioni all'interno delle proprietà che espone tale classe e richiamare il metodo InsertOnSubmit()
sulla proprietà Products
(che rappresenta la tabella dei prodotti). Infine, per effettuare una sorta di commit delle operazioni fatte, dobbiamo richiamare il metodo SubmitChanges()
del nostro DataContext
tipizzato.
Esempio di inserimento (C#)
Product product = new Product(); product.ProductName = "Maionese"; product.UnitPrice = 1.9m; Category category = new Category(); category.CategoryName = "Salse"; category.Products.Add(product); NorthwindDataContext northwind = new NorthwindDataContext(); using (northwind) { northwind.Categories.InsertOnSubmit(category); northwind.SubmitChanges(); }
Esempio di inserimento (VB.NET)
Dim product As New Product With product .ProductName = "Maionese" .UnitPrice = 1.9 End With Dim category As New Category With category .CategoryName = "Salse" .Products.Add(product) End With Dim northwind As New NorthwindDataContext Using (northwind) northwind.Categories.InsertOnSubmit(category) northwind.SubmitChanges() End Using
Dall'esempio si nota che abbiamo creato prima un oggetto di tipo Product
, poi uno di tipo Category
che al suo interno inserisce il prodotto creato precedentemente nella propria collezione di prodotti, attraverso questa sintassi: category.Products.Add(product)
.
Infine abbiamo effettuato la chiamata al metodo InsertOnSubmit()
solamente per l'oggetto Category
e non per il Product
. Sarà poi il DataContext
che eseguirà l'inserimento delle due entità.
Aggiornamento
Per quanto riguarda l'update di informazioni presenti nel nostro database SQL Server, non dobbiamo far altro che selezionare i dati da aggiornare tramite una query di selezione, aggiornare i valori delle proprietà che ci interessano e infine chiamare il metodo SubmitChanges()
dell'oggetto che rappresenta il contesto di persistenza.
Esempio di aggiornamento (C#)
NorthwindDataContext northiwd = new NorthwindDataContext(); using (northiwd) { Product maio = northiwd.Products.Single(p => p.ProductName == "Maionese"); maio.UnitPrice = 2.3m; northiwd.SubmitChanges(); }
Esempio di aggiornamento (VB.NET)
Dim northiwd As New NorthwindDataContext() Using (northiwd) Dim maio = (From p In northiwd.Products _ Where p.ProductName = "Maionese" _ Select p).Single() maio.UnitPrice = 2.3 northiwd.SubmitChanges() End Using
Tramite la sintassi C# 3.0, la selezione di un singolo prodotto viene facilitata attraverso l'utilizzo delle Lambda Expression.
Cancellazione
L'operazione di delete
è simile a quella appena vista di aggiornamento, con l'unica differenza che in questo caso viene chiamato il metodo DeleteOnSubmit()
che esplicita la cancellazione delle informazioni selezionate al successivo submit delle modifiche al database.
Esempio di cancellazione (C#)
NorthwindDataContext northwind = new NorthwindDataContext(); using (northwind) { Product maio = northwind.Products.Single(p => p.ProductName == "Maionese"); northwind.Products.DeleteOnSubmit(maio); northwind.SubmitChanges(); }
Esempio di cancellazione (VB.NET)
Dim northwind As New NorthwindDataContext() Using (northwind) Dim maio As Product = (From p In northwind.Products _ Where p.ProductName = "Maionese" _ Select p).Single() northwind.Products.DeleteOnSubmit(maio) northwind.SubmitChanges() End Using
Conclusioni
Linq to SQL è uno strumento molto potente per scrivere i DAL di applicazioni basate sulla versione 3.5 del .NET Framework, per gestire le operazioni sulla base di dati e per avvalerci di funzionalità come la persistenza delle informazioni in memoria.
Scegliere un ORM per gestire l'accesso ai dati risulta ottimale per applicazioni Web e desktop, ma è importante gestire correttamente il ciclo di vita del DataContext (apertura e chiusura, entity attached e detached, etc.).
Il limite posto da Linq to SQL è il supporto unico per database SQL Server (2000 o 2005). Tale limite verrà contenuto con l'uscita del framework Linq to Entities (parte integrante di ADO.NET Entity Framework, attualmente alla versione Beta 3).
Link utili
- Giancarlo Sudano - Architettura di Linq to SQL: Runtime e DataContext
- Scott Guthrie - Serie di post su Linq to SQL