Diciamo che il rientro dalle ferie è stato più duro del previsto. In questo tempo però ho avuto modo di approfondire alcuni argomenti piuttosto interessanti legati alla programmazione con il framework 3.5 in modo particolare con Linq.
Facendo una veloce ricerca su google possiamo accorgerci che le implementazioni custom di linq stanno crescendo in modo piuttosto veloce. Da quelle per fare le query su Amazon a quelle per cercare documenti in Sharepoint a tutto quello che vi può venire in mente. Questo è possibile perchè la sintassi introdotta con il framework 3.5 e che abbiamo visto utilizzare in LinqtoSql o in LinqtoXml non è legata alla implementazione di alcune librerie di accesso ai dati di Microsoft, ma è un nuovo tipo di sintassi introdotta a livello di linguaggio e supportata da 2 interfaccie che sono presenti nelle librerie base del framework 3.5. Quindi è sufficiente reimplementare le interfaccie di base per rendere i nostri oggetti pienamente compatibili con la sintassi Linq.
L'obiettivo di questo articolo è di comprendere come Linq viene interpretato a livello di compilatore e come viene utilizzato per effettuare ricerche sui dati. Arriveremo anche customizzare il comportamento di Linq per effettuare query su fonti dati non tradizionali. In TeamDev abbiamo seguito questo approccio per estendere le funzionalità del nostro layer di accesso ai dati al fine di sfruttare le interessantissime caratteristiche della sintassi Linq.
Le interfaccie di base
Abbiamo detto che linq si appoggia a due interfaccie :
- IEnumerable (presente da tempo nella libreria mscorlib)
- IQueryable (introdotta con il framework 3.5 nella libreria System.Core)
queste interfaccie vengono riconosciute dal compilatore e rendono possibile l'utilizzo della sintassi Linq. Comprendere come il compilatore utilizza le interfaccie, aiuta a comprendere come Linq funziona e come possiamo customizzarne il comportamento.
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
IEnumerable è presente dalla prima implementazione del framework a supporto della sintassi foreach. Possiamo infatti utilizzare il foreach solo sugli oggetti che implementano IEnumerable.
Siccome Linq utilizza IEnumerable per effettuare le query che coinvolgono collection e liste, questo ci lascia intuire che tutti i foreach possono essere trasformati in query Linq.
public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}
IQueryable è stata introdotta con la versione 3.5 del framework e si trova in System.Core. L'implementazione di IQueryable permette di customizzare il comportamento della query linq per adattarla ad una sorgente dati particolare.
Mentre le query su oggetti IEnumerable vengono eseguite in memoria , le query su IQueryable possono essere trasformate in query custom su oggetti di ogni tipo.
Tutto questo è possibile grazie alla classe Expression e alla interfaccia IQueryProvider esposte attraverso IQueryable
Come vengono utilizzate le interfaccie
Quando utilizzata con IQueryable, la query linq viene trasformata dal compilatore in un oggetto di tipo Expression (detto anche ExpressionTree , perchè ha una struttra ad albero).
L'oggetto Expression viene passato ad un provider che provvede ad interpretarlo e restituirà un risultato coerente con la query effettuata.
Quando utilizzata con IEnumerable, la query linq viene immediatamente eseguita attraverso degli extension methods che estendono le funzionalità della interfaccia IEnumerable.
E facile intuire quindi che per customizzare il comportamento delle query Linq dobbiamo implementare una delle suddette interfaccie.
Agendo con Reflector ci accorgiamo anche che il modo più corretto per intervenire è utilizzare IQueryable piuttosto che IEnumerable, questo perchè IEnumerable ha lo scopo di effettuare operazioni in memoria, cosa che probabilmente vorremo continuare a fare.
IQueryable invece è pensata per agire fuori dal contesto dell'applicazione e quindi è l'interfaccia che meglio si presta alla realizzazione di provider custom per l'accesso ai dati.
Nel prossimo post vedremo come è strutturata la classe Expression e come creare un parser per tradurre le Expression Tree in tutto ciò che vogliamo.