In questo breve articolo esaminiamo una "trappola" in cui sviluppatori .NET esperti (e non) possono incappare utilizzando il risultato di espressioni LINQ con il proprio linguaggio di riferimento (C#, Visual Basic o altri linguaggi).
Per illustrare lo scenario del problema, prendiamo il codice riportato qui di seguito, a puro titolo illustrativo, dove viene effettuata una query LINQ di selezione su una tabella di autori contenuta in un ipotetico database per blog (sfruttando Entity Framework oppure LINQ to SQL).
var allAuthors =
from a in authorDbTable
where a.BlogUrl == "edit.html.it"
orderby a.FullName
select a;
Fatta questa operazione, può capitare di utilizzare direttamente il risultato dell'espressione, ad esempio iterandolo con un ciclo foreach()
, oppure passandolo come parametro a uno o più metodi che accettano un IEnumerable
; consideriamo il caso di utilizzo rappresentato nel codice seguente:
foreach (var author in allAuthors)
Console.WriteLine("Autore: " + author.FullName);
// ...
var authorNames = new StringBuilder();
foreach (var author in allAuthors)
authorNames.Append(author.FullName + " ");
Supponiamo di utilizzare LINQ to SQL. Ciò che accade qui (e che può non "saltare all'occhio" immediatamente) è che la query SQL ottenuta dall'espressione LINQ viene eseguita più volte e ripetuta ad ogni utilizzo della variabile allAuthors
.
La spiegazione è molto semplice: il risultato di un'espressione LINQ, cioè il valore di allAuthors
nel nostro esempio, rappresenta appunto un'espressione; si tratta di un'oggetto che contiene al suo interno quelle informazioni specificate tramite il costrutto LINQ, e non gli oggetti creati dalla query su DB con cui vogliamo lavorare.
In un ciclo come nell'esempio di cui sopra, l'espressione definita con LINQ viene interpretata e "trasformata" in una query SQL per creare e inizializzare quegli oggetti "veraci" (che fanno parte del nostro business model) su cui il ciclo deve operare. Se abbiamo tre cicli, la query viene eseguita tre volte.
Il meccanismo in questione è noto come deferred execution: l'esecuzione effettiva dell'interrogazione espressa con LINQ avviene solo nel momento in cui è necessario passare dall'espressione al risultato con cui si intende operare.
Come risolvere quindi il nostro problema?
In realtà, il "problema" non è tale, bensì è un vantaggio: attraverso questo meccanismo, noi possiamo creare un'espressione e riutilizzarla in altre più complesse, creando dinamicamente un'espressione unica e articolata che viene eseguita "in un colpo solo" (e quindi ottimizzata il più possibile, se parliamo di traduzione in SQL) nel momento in cui abbiamo bisogno dei risultati corrispondenti.
Nel caso preso ad esempio, è sufficiente invocare uno dei tanti metodi che "eseguono" esplicitamente l'espressione LINQ e restituiscono un array dei valori richiesti (con il metodo ToArray
), oppure una lista (con ToList
), oppure il primo elemento (con First
) e così via.
Per correggere la nostra implementazione dal punto di vista pratico, possiamo quindi procedere in questo modo:
var qryAuthors =
from a in authorDbTable
where a.BlogUrl == "edit.html.it"
orderby a.FullName
select a;
var allAuthors = qryAuthors.ToList();
Lo stesso principio può influire sui cicli in cui, preso un insieme di oggetti, andiamo a modificarne le proprietà (si veda ad esempio questo approfondimento sui weblog ufficiali di ASP.NET.
Se siete a conoscenza di quanto descritto nell'articolo, buon per voi. :) In caso contrario, ricontrollate attentamente il vostro codice poiché "abusare" delle espressioni LINQ in questo modo può introdurre cicli ripetuti inutilmente e l'esecuzione reiterata di query SQL identiche, con ovvio decadimento di performance della vostra applicazione.
Link utili
- Articolo di Charlie Calvert per ulteriori approfondimenti e altri esempi sulla Deferred Execution
- Documentazione MSDN