Introduzione alle query LINQ (C#)
Una query è un'espressione che recupera dati da un'origine dati. Le query sono espresse generalmente in un linguaggio di query specializzato. Nel tempo sono stati sviluppati diversi linguaggi per i vari tipi di origini dati, ad esempio SQL per database relazionali e XQuery per XML. Gli sviluppatori hanno dovuto pertanto imparare un nuovo linguaggio di query per ogni tipo di origine dati o formato dati supportato. LINQ semplifica questa situazione offrendo un modello coerente per l'utilizzo dei dati con tutti i diversi tipi di origini e formati dati. In una query LINQ vengono utilizzati sempre gli oggetti. Vengono utilizzati gli stessi modelli di codifica di base per eseguire una query e trasformare i dati in documenti XML, database SQL, dataset ADO.NET, insiemi .NET e qualsiasi altro formato per il quale sia disponibile un provider LINQ.
Tre parti di un'operazione di query
Tutte le operazioni di query LINQ sono costituite da tre azioni distinte:
Ottenere l'origine dati.
Creare la query.
Eseguire la query.
Nell'esempio seguente viene illustrato come le tre parti di un'operazione di query vengono espresse nel codice sorgente. Nell'esempio viene utilizzata una matrice di valori interi come origine dati per motivi di praticità. Gli stessi concetti si applicano però anche ad altre origini dati. In questo argomento si fa riferimento sempre a tale esempio.
class IntroToLINQ
{
static void Main()
{
// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
from num in numbers
where (num % 2) == 0
select num;
// 3. Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
}
}
Nella figura seguente viene illustrata l'operazione di query completa. In LINQ l'esecuzione della query è distinta dalla query stessa; in altre parole i dati non vengono recuperati solo creando una variabile di query.
Origine dati
Poiché nell'esempio precedente è stata utilizzata una matrice come origine dati, viene supportata implicitamente l'interfaccia generica IEnumerable<T>, ed è pertanto possibile eseguire una query con LINQ. Una query viene eseguita in un'istruzione foreach e foreach richiede IEnumerable o IEnumerable<T>. I tipi che supportano IEnumerable<T> o un'interfaccia derivata, ad esempio l'oggetto generico IQueryable<T>, vengono denominati tipi queryable.
Un tipo queryable non richiede alcuna modifica o trattamento speciale per essere utilizzato come origine dati LINQ. Se i dati di origine non sono già in memoria come tipi queryable, il provider LINQ deve rappresentarli come tali. Ad esempio, LINQ to XML carica un documento XML in un tipo XElement queryable:
// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");
Con LINQ to SQL è necessario creare prima un mapping relazionale a oggetti in fase di progettazione, manualmente o utilizzando Progettazione relazionale oggetti. È possibile quindi scrivere le query sugli oggetti e in fase di esecuzione LINQ to SQL gestisce la comunicazione con il database. Nell'esempio seguente Customers rappresenta una tabella specifica nel database e il tipo del risultato della query, IQueryable<T>, deriva da IEnumerable<T>.
Northwnd db = new Northwnd(@"c:\northwnd.mdf");
// Query for customers in London.
IQueryable<Customer> custQuery =
from cust in db.Customers
where cust.City == "London"
select cust;
Per ulteriori informazioni sulla creazione di tipi specifici di origini dati, vedere la documentazione dei diversi provider LINQ. La regola di base è comunque molto semplice: un'origine dati LINQ è rappresentata da qualsiasi oggetto che supporti l'interfaccia generica IEnumerable<T> o un'interfaccia da essa ereditata.
Nota
È inoltre possibile utilizzare come origine dati LINQ i tipi quali ArrayList, che supportano l'interfaccia non generica IEnumerable. Per ulteriori informazioni, vedere la classe Procedura: eseguire una query su un ArrayList con LINQ.
Query
La query specifica le informazioni da recuperare dall'origine o dalle origini dati. Una query può anche specificare il modo in cui ordinare, raggruppare e definire le informazioni prima che vengano restituite. Una query viene archiviata in una variabile di query e inizializzata con un'espressione di query. Per semplificare la scrittura delle query, in C# è stata introdotta una nuova sintassi della query.
La query nell'esempio precedente restituisce tutti i numeri pari dalla matrice di valori interi. L'espressione di query contiene tre clausole: from, where e select. Se si ha dimestichezza con SQL, si sarà notato che l'ordine delle clausole è inverso rispetto all'ordine in SQL. La clausola from specifica l'origine dati, la clausola where applica il filtro e la clausola select specifica il tipo degli elementi restituiti. Queste e le altre clausole di query vengono illustrate dettagliatamente nella sezione LINQ Query Expressions (C# Programming Guide). L'aspetto importante per il momento è che in LINQ la variabile di query stessa non effettua alcuna azione e non restituisce dati. Archivia solo le informazioni richieste per generare i risultati quando successivamente viene eseguita la query. Per ulteriori informazioni sul modo in cui le query vengono costruite automaticamente, vedere Cenni preliminari sugli operatori di query standard.
Nota
Le query possono inoltre essere espresse utilizzando la sintassi del metodo. Per ulteriori informazioni, vedere Confronto tra sintassi di query LINQ e sintassi dei metodi (C#).
Esecuzione della query
Esecuzione posticipata
Come indicato in precedenza, la variabile di query stessa archivia solo i comandi della query. L'esecuzione effettiva della query è rinviata finché non si scorre la variabile di query in un'istruzione foreach. Questo concetto è denominato esecuzione rinviata e viene illustrato nell'esempio seguente:
// Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
Dall'istruzione foreach vengono inoltre recuperati i risultati della query. Ad esempio, nella query precedente la variabile di iterazione num contiene ogni valore (uno alla volta) della sequenza restituita.
Poiché la variabile di query stessa non contiene mai i risultati della query, è possibile eseguirla un numero illimitato di volte. Ad esempio, è possibile avere un database che viene aggiornato continuamente mediante un'applicazione separata. Nell'applicazione è possibile creare una query che recupera i dati più recenti ed eseguirla ripetutamente a determinati intervalli per recuperare ogni volta risultati diversi.
Esecuzione immediata
Le query che eseguono funzioni di aggregazione su un intervallo di elementi di origine devono prima scorrere tali elementi. Esempi di tali query sono Count, Max, Average e First. Queste query vengono eseguite senza un'istruzione foreach esplicita perché la query stessa deve utilizzare foreach per poter restituire un risultato. Tenere inoltre presente che questi tipi di query restituiscono un solo valore, non un insieme IEnumerable. Nella query seguente viene restituito un conteggio dei numeri pari nella matrice di origine:
var evenNumQuery =
from num in numbers
where (num % 2) == 0
select num;
int evenNumCount = evenNumQuery.Count();
Per forzare l'esecuzione immediata di una query e memorizzarne nella cache i risultati, è possibile chiamare i metodi ToList<TSource> o ToArray<TSource>.
List<int> numQuery2 =
(from num in numbers
where (num % 2) == 0
select num).ToList();
// or like this:
// numQuery3 is still an int[]
var numQuery3 =
(from num in numbers
where (num % 2) == 0
select num).ToArray();
È inoltre possibile forzare l'esecuzione inserendo il ciclo foreach immediatamente dopo l'espressione di query. Tuttavia, chiamando ToList o ToArray vengono memorizzati nella cache anche tutti i dati di un singolo oggetto dell'insieme.
Vedere anche
Attività
Riferimenti
Concetti
Cenni preliminari su Progettazione relazionale oggetti
LINQ Query Expressions (C# Programming Guide)