Introduzione alle query LINQ (C#)

Una query è un'espressione che recupera dati da un'origine dati. Origini dati diverse hanno linguaggi di query nativi diversi, ad esempio SQL per database relazionali e XQuery per XML. Gli sviluppatori devono imparare un nuovo linguaggio di query per ogni tipo di origine dati o formato dati supportato. LINQ semplifica questa situazione offrendo un modello di linguaggio C# coerente per tipi di origini dati e formati. In una query LINQ si lavora sempre con oggetti C#. Vengono usati gli stessi criteri di codifica di base per eseguire una query e trasformare i dati in documenti XML, database SQL, raccolte .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:

  1. Ottenere l'origine dati.
  2. Creare la query.
  3. 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 usata 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 articolo si fa riferimento sempre a tale esempio.

// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 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, non si recuperano dati creando una variabile di query.

Diagram of the complete LINQ query operation.

Origine dati

L'origine dati nell'esempio precedente è una matrice che supporta l'interfaccia generica IEnumerable<T>. Ciò significa che è possibile eseguire query con LINQ. Viene eseguita una query in un'istruzione foreach e foreach richiede IEnumerable o IEnumerable<T>. I tipi che supportano IEnumerable<T> o un'interfaccia derivata, ad esempio l'interfaccia generica IQueryable<T> sono denominati tipi queryable.

Un tipo queryable non richiede alcuna modifica o trattamento speciale per essere usato 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 EntityFramework si crea un mapping relazionale a oggetti tra le classi C# e lo schema del database. È possibile scrivere le query sugli oggetti e in fase di esecuzione EntityFramework 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 altre informazioni sulla creazione di tipi specifici di origini dati, vedere la documentazione dei diversi provider LINQ. Tuttavia, la regola di base è semplice: un'origine dati LINQ è qualsiasi oggetto che supporti l'interfaccia generica IEnumerable<T> o un'interfaccia da essa ereditata, in genere IQueryable<T>.

Nota

È anche possibile usare come origine dati LINQ tipi come ArrayList che supportano l'interfaccia non generica IEnumerable. Per altre informazioni, vedere Come eseguire una query su un ArrayList con LINQ (C#).

La 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 di essere restituite. Una query viene archiviata in una variabile di query e inizializzata con un'espressione di query. Si usa la sintassi di query C# per scrivere 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. Tutte le clausole di query sono descritte in dettaglio nella presente sezione. 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 altre informazioni sul modo in cui le query vengono costruite, vedere Cenni preliminari sugli operatori di query standard (C#).

Nota

Le query possono anche essere espresse usando la sintassi del metodo. Per altre informazioni, vedere Sintassi di query e sintassi di metodi in LINQ.

Esecuzione di query

Esecuzione posticipata

La variabile di query archivia solo i comandi di query. L'esecuzione effettiva della query è rinviata finché non si esegue l'iterazione della variabile di query in un'istruzione foreach. Questo concetto è detto esecuzione posticipata e viene illustrato nell'esempio seguente:

foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

Dall'istruzione foreach vengono anche 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 ripetutamente per recuperare i dati aggiornati. È ad esempio possibile disporre di un database che viene aggiornato continuamente da un'applicazione separata. Nell'applicazione è possibile creare una query che recupera i dati più recenti ed è possibile eseguirla a intervalli per recuperare i risultati aggiornati.

Esecuzione immediata

Le query che eseguono funzioni di aggregazione su un intervallo di elementi di origine devono prima eseguire l'iterazione in tali elementi. Esempi di tali query sono Count, Max, Average e First. Queste metodi vengono eseguiti senza un'istruzione foreach esplicita poiché la query stessa deve usare foreach per poter restituire un risultato. Queste query restituiscono un singolo valore, non una raccolta 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 o ToArray.

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();

È anche 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 della raccolta.

Vedi anche