Programming Languages Hacks

Importanti regole per linguaggi di programmazione rilevanti come Java, C, C++, C#…

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

Linq: Panoramica delle Funzionalità

Posted by Ricibald on March 7th, 2008

Prima di leggere questo post consiglio a chi non ne fosse a conoscenza di leggere le novità del Framework 3.5 di .NET.

La più grande novità per lo sviluppatore inclusa nel .NET Framework 3.5 è senza dubbio LINQ. LINQ è l’acronimo per Language INtegrated Query.

Cosa è LINQ

LINQ non è altro che un’estensione del linguaggio che permette di eseguire query verso una determinata risorsa. La sintassi di query rimane la stessa e la risorsa può essere:

  • un file XML (LINQ To Xml): più semplice e veloce rispetto a utilizzare XPath
  • una collection di classi (LINQ To Objects): più semplice che ciclare gli elementi uno a uno
  • un database (LINQ To SQL): più sicuro e performante che invocare sql realizzate a run-time

La maggior parte delle funzioni di LINQ si basano sulle nuove caratteristiche del Framework. Soprattutto:

Di fatto la parte più interessante è il LINQ To SQL e rappresenta l’implementazione Microsoft della tecnica ORM.
Supporta transazioni, viste e stored procedure. Fornisce inoltre un semplice modo per integrare validazione dei dati e logica di business direttamente nel modello dati.

Come funziona LINQ

Come funziona? Semplice: Eseguo “Add -> New Item -> Linq To SQL Classes” e creo Universita.dbml.
Nel designer si possono trascinare tabelle e stored procedure e verranno automaticamente create:

  • classe UniversitaDataContext, che contiene tutti gli oggetti (Universitas, …) e metodi (stored procedure) del db, tipizzati fortemente
  • classi varie che rappresentano la versione tipizzata delle singole tabelle (Universita, Studente, …)

Realizza in questo modo un efficace ORM. Le classi sono fortemente tipizzate e le proprieta’ inserite devono essere quindi conformi al
tipo dichiarato nello schema del db, altrimenti verranno sollevati errori di compilazione (impossibile convertire da string a int…).
Il collegamento tra i controlli web e LINQ si ottiene tramite il nuovo LinqDataSource.

Un Esempio Pratico

Immaginiamo di avere un semplice db con due tabelle: studente e università, in cui studente ha un vincolo di integrità referenziale
verso università. Dopo aver trascinato le due tabelle nel designer e aver salvato il file Universita.dbml possiamo utilizzare le classi
generate nel seguente modo:

UniversitaDataContext db = new UniversitaDataContext();
Studente s = new Studente() { Matricola = "1234" };
Universita u = new Universita() { Name = "Roma Tre" };
u.Studenti.Add(s);
db.Universitas.Add(u);
db.SubmitChanges();

Questo creerà la corrispondente istruzione SQL trasferendo in modo atomico le modifiche apportate all’oggetto dall’ultimo submit:
mantiene quindi un sistema di versioning in modo automatico e un contesto di transazione.
Le classi generate sono parziali, e questo consente di apportare modifiche e/o aggiunte alla classe generata dal designer.

Personalizzazione del comportamento

Le proprieta’ (colonne) possono essere caricate su richiesta (lazy loading) impostando a true la proprieta’ Delay Loaded.

Le classi definiscono dei punti di estensibilità tramite metodi partial, che rappresentano una novità del framework 3.5. In tali punti si possono definire le azioni da eseguire prima e/o dopo aver settato una proprietà. Risultano utili per inserire validazione dei dati non verificabili dal db, come il check del formato email. I metodi parziali che vengono definiti sono:

partial void OnXXXChanging(YYY value);
partial void OnXXXChanged();

dove XXX è il nome della proprietà, e YYY il suo tipo.

In questo modo possiamo validare il formato dei dati, ma non la logica di inserimento. Se ad esempio vogliamo che la proprietà DataIscrizione
sia minore di DataLaurea allora dobbiamo eseguire tale controllo in un punto separato. Per questo viene messo a disposizione un ulteriore
metodo parziale per gestire questo differente tipo di logica di validazione:

partial void OnValidate(ChangeAction action);

In modo analogo, per definire una logica customizzata prima di un INSERT, UPDATE e DELETE, nella classe UniversitaDataContext
saranno disponibili i punti di estensione:

partial void InsertStudente(Studente instance);
partial void UpdateStudente(Studente instance);
partial void DeleteStudente(Studente instance);
partial void InsertUniversita(Universita instance);
partial void UpdateUniversita(Universita instance);
partial void DeleteUniversita(Universita instance);

Normalmente saranno utilizzate nel seguente modo:

public partial class UniversitaDataContext {
    partial void InsertStudente(Studente instance) {
        // TODO: custom insert validation logic
        this.ExecuteDynamicInsert(instance);
    }
}

Stored Procedure

Le stored procedure possono essere aggiunte semplicemente trascinandole nel designer. Se non restituiscono un tipo specifico
(Studente o Universita) non ha senso utilizzare quello automaticamente generato (che potrebbe cambiare in caso di conflitti).
Utilizzare var in questo caso. Un esempio pratico: trascino la stored procedure GetMatricoleStudenti nel designer e scrivo:

UniversitaDataContext db = new UniversitaDataContext();
foreach(Universita univ in db.Universitas) {
    var matricoleStudenti = db.GetMatricoleStudenti(univ.Id);
    foreach(var matricolaStudente in matricoleStudenti) {
        Console.WriteLine(matricolaStudente.Matricola);
    }
}

Se le stored procedure restituiscono un insieme di dati compatibile con una determinata tabella (ad es.Studente), allora si può trascinare la stored procedure
direttamente NELLA tabella e tale stored procedure restituirà quindi un risultato IEnumerable<Studente>.

Si possono anche gestire risultati con forme multiple (una stored procedure potrebbe restituire diversi record set a seconda
del flusso dell’operazione). In questo caso non possiamo trascinare la stored procedure nel modo usuale, ma richiamarla “a mano” via codice
(sempre nella classe UniversitaDataContext) dichiarando le possibili classi restituite tramite l’attributo ResultTypeAttribute.

Si possono anche definire in ogni tabella operazioni personalizzate di INSERT, DELETE, UPDATE in modo da collegarle
a una particolare stored procedure.

Conclusioni

Ricordate quando avevo detto che LINQ To SQL rappresenta l’implementazione Microsoft ORM? Non è completamente vero… Di fatto rappresenta un modo tipizzato per accedere alle proprietà del db. Osserviamo ad esempio l’analisi di Pietro Brambati. La relazione molti-a-molti autore-libro viene fatta corrispondere alle tre tabelle che il db rappresenta. Un’altra critica che è stata mossa è il fatto che le classi generate (Studente, Università, …) sono di fatto “sporcate” con tanti attributi e metodi che non si vorrebbero vedere nei
propri oggetti di dominio.

Per questo esiste il progetto LINQ to Entities: un’altra implementazione di LINQ fatta per parlare con l’ADO.NET Entity Framework (EF). Sia l’EF che LINQ to Entities sono attualmente in Beta 3. L’EF è un framework che consentirà agli sviluppatori di lavorare con un maggior livello di astrazione; cioè uno sviluppatore si concentrerà solo sul modello concettuale proprio del modello Entità-Relazione, in maniera indipendente dallo storage sottostante sia esso SQL Server o un altro database.

LINQ to Entities realizza un VERO ORM, supportando quindi relazioni molti-a-molti e generalizzazioni e separando il mapping da relazionale a oggetti in file xml separati, in modo da non inquinare l’interfaccia delle classi generate. Come detto, siamo in una versione non completamente matura e per questo è stato deciso di posticipare l’uscita di questa branca a circa sei mesi dopo l’uscita di Visual Studio 2008 e perciò l’infrastruttura attualmente presente potrebbe subire forti cambiamenti.

Maggiori informazioni sulle differenze tra LINQ To SQL e LINQ To Entities sono riportate nel post di Kevin Hoffman.

Leave a Reply

You must be logged in to post a comment.