Nozioni fondamentali sulle espressioni di query (Guida per programmatori C#)

Definizione e scopo di una query

Una query è un set di istruzioni che descrive quali dati recuperare da una o più origini dati specificate e quale forma e organizzazione devono avere i dati restituiti. Una query è distinta dai risultati che produce.

I dati di origine sono in genere organizzati in modo logico, come sequenza di elementi dello stesso tipo. Una tabella di database SQL contiene una sequenza di righe. In modo analogo, un oggetto DataTable contiene una sequenza di oggetti DataRow. In un file XML, esiste una "sequenza" di elementi XML (anche se questi sono organizzati gerarchicamente in una struttura ad albero). Un insieme in memoria contiene una sequenza di oggetti.

Dal punto di vista di un'applicazione, il tipo e la struttura specifici dei dati di origine di partenza non sono importanti. L'applicazione considera sempre i dati di origine un insieme IEnumerable<T> o IQueryable<T>. In LINQ to XML, i dati di origine sono resi visibili come IEnumerable<XElement>. In LINQ to DataSet, si tratta di un IEnumerable<DataRow>. In LINQ to SQL, si tratta di un IEnumerable o IQueryable di qualsiasi oggetto personalizzato definito per rappresentare i dati nella tabella SQL.

Data questa sequenza di origine, una query può eseguire una delle tre azioni seguenti:

  • Recuperare un subset degli elementi per produrre una nuova sequenza senza modificare i singoli elementi. La query può quindi ordinare o raggruppare la sequenza restituita in svariati modi, come illustrato nell'esempio seguente (si supponga che scores sia int[]):

    IEnumerable<int> highScoresQuery =
        from score in scores
        where score > 80
        orderby score descending
        select score;
    
  • Recuperare una sequenza di elementi come nell'esempio precedente, ma trasformarli in un nuovo tipo di oggetto. Una query, ad esempio, può recuperare solo i cognomi da determinati record cliente in un'origine dati oppure può recuperare il record completo e quindi utilizzarlo per costruire un altro tipo di oggetto in memoria o persino dati XML prima di generare la sequenza di risultati finale. Nell'esempio riportato di seguito viene illustrata una trasformazione da int a string. Si noti il nuovo tipo di highScoresQuery.

    IEnumerable<string> highScoresQuery2 =
        from score in scores
        where score > 80
        orderby score descending
        select String.Format("The score is {0}", score);
    
  • Recuperare un valore singleton sui dati di origine, ad esempio:

    • Numero di elementi che corrispondono a una determinata condizione.

    • Elemento con il valore maggiore o minore.

    • Primo elemento che corrisponde a una condizione o somma di particolari valori in un set specificato di elementi. Nella query seguente, ad esempio, viene restituito il numero di punteggi superiori a 80 dalla matrice di integer scores:

    int highScoreCount =
        (from score in scores
         where score > 80
         select score)
         .Count();
    

    Nell'esempio precedente, si noti l'utilizzo di parentesi nell'espressione di query prima della chiamata al metodo Count. È inoltre possibile esprimere ciò utilizzando una variabile nuova per archiviare il risultato concreto. Questa tecnica è più leggibile perché mantiene la variabile che archivia la query separata dalla query che archivia un risultato.

    IEnumerable<int> highScoresQuery3 =
        from score in scores
        where score > 80
        select score;
    
    int scoreCount = highScoresQuery3.Count();
    

Nell'esempio precedente, la query viene eseguita nella chiamata a Count, perché Count deve scorrere i risultati per determinare il numero di elementi restituiti da highScoresQuery.

Definizione di espressione di query

Un'espressione di query è una query espressa nella sintassi della query. Un'espressione di query è un costrutto di linguaggio di prima categoria. È analoga a qualsiasi altra espressione e può essere utilizzata in qualsiasi contesto in cui è valida un'espressione C#. Un'espressione di query è costituita da un set di clausole scritte in una sintassi dichiarativa analoga a SQL o a XQuery. Ogni clausola contiene a sua volta una o più espressioni C# e queste espressioni possono essere un'espressione di query o contenere un'espressione di query.

Un'espressione di query deve iniziare con una clausola from e terminare con una clausola select o group. Tra la prima clausola from e l'ultima clausola select o group, può essere contenuta una o più delle seguenti clausole facoltative: where, orderby, join, let nonché clausole from aggiuntive. È inoltre possibile utilizzare la parola chiave into per consentire al risultato di una clausola join o group di fungere da origine per le clausole query aggiuntive nella stessa espressione di query.

Variabile di query

In LINQ, una variabile di query è qualsiasi variabile che archivia una query anziché i risultati di una query. In particolare, una variabile di query è sempre un tipo enumerabile che produrrà una sequenza di elementi quando viene scorso in un'istruzione foreach o in una chiamata diretta al metodo IEnumerator.MoveNext.

Nell'esempio di codice seguente viene illustrata un'espressione di query semplice con un'origine dati, una clausola di filtro, una clausola di ordinamento e nessuna trasformazione degli elementi di origine. La clausola select termina la query.

static void Main()
{
    // Data source.
    int[] scores = { 90, 71, 82, 93, 75, 82 };

    // Query Expression.
    IEnumerable<int> scoreQuery = //query variable
        from score in scores //required
        where score > 80 // optional
        orderby score descending // optional
        select score; //must end with select or group

    // Execute the query to produce the results
    foreach (int testScore in scoreQuery)
    {
        Console.WriteLine(testScore);
    }                  
}
// Outputs: 93 90 82 82      

Nell'esempio precedente, scoreQuery è una variabile di query che viene talvolta semplicemente definita query. La variabile della query non archivia alcun dato del risultato effettivo, che viene prodotto nel ciclo foreach. Quando l'istruzione foreach viene eseguita, i risultati della query non sono restituiti tramite la variabile della query scoreQuery. Vengono invece restituiti tramite la variabile di iterazione testScore. La variabile scoreQuery può essere iterata in un secondo ciclo foreach. Produrrà gli stessi risultati purché non vengano modificate né la variabile stessa né l'origine dati.

Una variabile di query può archiviare una query espressa nella sintassi della query o nella sintassi del metodo oppure in una combinazione delle due. Negli esempi seguenti, queryMajorCities e queryMajorCities2 sono variabili di query:

//Query syntax
IEnumerable<City> queryMajorCities =
    from city in cities
    where city.Population > 100000
    select city;


// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);

Nei due esempi seguenti vengono invece illustrate variabili che non sono variabili di query anche se ognuna viene inizializzata con una query. Non sono variabili di query perché archiviano risultati:

int highestScore =
    (from score in scores
     select score)
    .Max();

// or split the expression
IEnumerable<int> scoreQuery =
    from score in scores
    select score;

int highScore = scoreQuery.Max();

List<City> largeCitiesList =
    (from country in countries
     from city in country.Cities
     where city.Population > 10000
     select city)
       .ToList();

// or split the expression
IEnumerable<City> largeCitiesQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

List<City> largeCitiesList2 = largeCitiesQuery.ToList();

Nota

Nella documentazione di LINQ, nei nomi delle variabili che archiviano una query è presente la parola "query". Nei nomi delle variabili che archiviano un risultato effettivo non appare "query".

Per ulteriori informazioni sulle diverse modalità di espressione delle query, vedere Confronto tra sintassi di query LINQ e sintassi dei metodi (C#).

Tipizzazione esplicita e implicita di variabili di query

In questa documentazione viene in genere fornito il tipo esplicito della variabile della query al fine di illustrare la relazione del tipo tra la variabile della query e la clausola select. È anche tuttavia possibile utilizzare la parola chiave var per indicare al compilatore di dedurre il tipo di una variabile di query (o di qualsiasi altra variabile locale) in fase di compilazione. La query di esempio illustrata in precedenza in questo argomento può essere espressa anche utilizzando la tipizzazione implicita:

// Use of var is optional here and in all queries.
// queryCities is an IEnumerable<City> just as 
// when it is explicitly typed.
var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

Per ulteriori informazioni, vedere Variabili locali tipizzate in modo implicito (Guida per programmatori C#) e Relazioni tra i tipi nelle operazioni di query LINQ (C#).

Inizio di un'espressione di query

Un'espressione di query deve iniziare con la clausola from. Specifica un'origine dati insieme a una variabile di intervallo. La variabile di intervallo, che rappresenta ogni elemento successivo nella sequenza di origine quando la sequenza di origine viene attraversata, è fortemente tipizzata in base al tipo degli elementi nell'origine dati. Nell'esempio seguente, poiché countries è una matrice di oggetti Country, la variabile di intervallo viene tipizzata anche come Country. Poiché la variabile di intervallo è fortemente tipizzata, è possibile utilizzare l'operatore punto per accedere a qualsiasi membro disponibile del tipo.

IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 500000 //sq km
    select country;

La variabile di intervallo si trova nell'ambito finché la query non viene chiusa con un punto e virgola o con una clausola di continuazione.

Un'espressione di query può contenere più clausole from. Utilizzare clausole from aggiuntive quando ogni elemento nella sequenza di origine è un insieme o contiene un insieme. Si supponga, ad esempio, di disporre di un insieme di oggetti Country, ognuno dei quali contiene un insieme di oggetti City denominati Cities. Per eseguire una query sugli oggetti City in ogni Country, utilizzare due clausole from come illustrato di seguito:

IEnumerable<City> cityQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

Per ulteriori informazioni, vedere Clausola from (Riferimento C#).

Fine di un'espressione di query

Un'espressione di query deve terminare con una clausola select o con una clausola group.

Clausola group

Utilizzare la clausola group per produrre una sequenza di gruppi organizzati da una chiave specificata. La chiave può essere qualsiasi tipo di dato. Nella query seguente, ad esempio, viene creata una sequenza di gruppi che contiene uno o più oggetti Country e la cui chiave è un valore di char.

var queryCountryGroups =
    from country in countries
    group country by country.Name[0];

Per ulteriori informazioni sul raggruppamento, vedere Clausola group (Riferimento C#).

Clausola select

Utilizzare la clausola select per produrre tutti gli altri tipi di sequenze. Una clausola select semplice produce solo una sequenza di oggetti dello stesso tipo degli oggetti contenuti nell'origine dati. In questo esempio, l'origine dati contiene oggetti Country. La clausola orderby ordina semplicemente gli elementi in un ordine nuovo e la clausola select produce una sequenza degli oggetti Country riordinati.

IEnumerable<Country> sortedQuery =
    from country in countries
    orderby country.Area
    select country;

La clausola select può essere utilizzata per trasformare i dati di origine in sequenze di nuovi tipi. Questa trasformazione è detta anche proiezione. Nell'esempio seguente, la clausola select proietta una sequenza di tipi anonimi che contiene solo un subset dei campi nell'elemento originale. Si noti che i nuovi oggetti vengono inizializzati utilizzando un inizializzatore di oggetto.

// Here var is required because the query
// produces an anonymous type.
var queryNameAndPop =
    from country in countries
    select new { Name = country.Name, Pop = country.Population };

Per ulteriori informazioni su tutti i modi in cui una clausola select può essere utilizzata per trasformare i dati di origine, vedere Clausola select (Riferimento C#).

Continuazioni con "into"

È possibile utilizzare la parola chiave into in una clausola select o group per creare un identificatore temporaneo che archivia una query. Ricorrere a questa operazione quando è necessario eseguire operazioni aggiuntive su una query dopo un'operazione di raggruppamento o di selezione. Nel seguente esempio gli oggetti countries vengono raggruppati a seconda della popolazione in intervalli di 10 milioni. Dopo la creazione di questi gruppi, clausole aggiuntive ne filtrano alcuni e quindi li ordinano in ordine crescente. Per eseguire tali operazioni aggiuntive, è necessaria la continuazione rappresentata da countryGroup.

// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
    from country in countries
    let percentile = (int) country.Population / 10000000
    group country by percentile into countryGroup
    where countryGroup.Key >= 20
    orderby countryGroup.Key
    select countryGroup;

// grouping is an IGrouping<int, Country>
foreach (var grouping in percentileQuery)
{
    Console.WriteLine(grouping.Key);
    foreach (var country in grouping)
        Console.WriteLine(country.Name + ":" + country.Population);
}

Per ulteriori informazioni, vedere into (Riferimenti per C#).

Filtro, ordinamento e unione

Tra la clausola from iniziale e la clausola select o group finale, tutte le altre clausole (where, join, orderby, from, let) sono facoltative. Qualsiasi clausola facoltativa può essere utilizzata da zero a più volte in un corpo di query.

Clausola where

Utilizzare la clausola where per filtrare elementi dai dati di origine in basa a una o più espressioni di predicato. La clausola where nell'esempio seguente presenta due predicati.

IEnumerable<City> queryCityPop =
    from city in cities
    where city.Population < 200000 && city.Population > 100000
    select city;

Per ulteriori informazioni, vedere Clausola where (Riferimento C#).

Clausola orderby

Utilizzare la clausola orderby per ordinare i risultati in ordine crescente o decrescente. È anche possibile specificare ordinamenti secondari. Nell'esempio seguente viene eseguito un ordinamento primario sugli oggetti country utilizzando la proprietà Area e quindi un ordinamento secondario utilizzando la proprietà Population.

IEnumerable<Country> querySortedCountries =
    from country in countries
    orderby country.Area, country.Population descending
    select country;

La parola chiave ascending è facoltativa e costituisce l'ordinamento predefinito se non viene specificato alcun ordine. Per ulteriori informazioni, vedere Clausola orderby (Riferimento C#).

Clausola join

Utilizzare la clausola join per associare e/o combinare gli elementi di un'origine dati con gli elementi di un'altra origine dati in base a un confronto di uguaglianza tra chiavi specificate in ogni elemento. In LINQ, le operazioni di join vengono eseguite su sequenze di oggetti i cui elementi sono tipi diversi. Dopo avere unito due sequenze, è necessario utilizzare un'istruzione select o group per specificare quale elemento archiviare nella sequenza di output. È anche possibile utilizzare un tipo anonimo per combinare proprietà da ogni set di elementi associati in un nuovo tipo per la sequenza di output. Nell'esempio seguente vengono associati gli oggetti prod la cui proprietà Category corrisponde a una delle categorie nella matrice di stringhe categories. Vengono filtrati i prodotti la cui proprietà Category non corrisponde a nessuna stringa in categories. L'istruzione select proietta un nuovo tipo le cui proprietà sono accettate sia da cat che da prod.

var categoryQuery =
    from cat in categories
    join prod in products on cat equals prod.Category
    select new { Category = cat, Name = prod.Name };

È anche possibile eseguire un group join archiviando i risultati dell'operazione di join in una variabile temporanea utilizzando la parola chiave in. Per ulteriori informazioni, vedere Clausola join (Riferimento C#).

Clausola let

Utilizzare la clausola let per archiviare il risultato di un'espressione, ad esempio una chiamata al metodo, in una nuova variabile di intervallo. Nell'esempio seguente, la variabile di intervallo firstName archivia il primo elemento della matrice di stringhe restituita da Split.

string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" };
IEnumerable<string> queryFirstNames =
    from name in names
    let firstName = name.Split(new char[] { ' ' })[0]
    select firstName;

foreach (string s in queryFirstNames)
    Console.Write(s + " ");
//Output: Svetlana Claire Sven Cesar

Per ulteriori informazioni, vedere Clausola let (Riferimento C#).

Sottoquery in un'espressione di query

Una clausola query può contenere un'espressione di query, che viene talvolta detta sottoquery. Ogni sottoquery inizia con la propria clausola from che non punta necessariamente alla stessa origine dati nella prima clausola from. Nella query seguente, ad esempio, viene illustrata un'espressione di query utilizzata nell'istruzione select per recuperare i risultati di un'operazione di raggruppamento.

var queryGroupMax =
    from student in students
    group student by student.GradeLevel into studentGroup
    select new
    {
        Level = studentGroup.Key,
        HighestScore =
            (from student2 in studentGroup
             select student2.Scores.Average())
             .Max()
    };

Per ulteriori informazioni, vedere Procedura: eseguire una sottoquery su un'operazione di raggruppamento (Guida per programmatori C#).

Vedere anche

Concetti

Guida per programmatori C#

Espressioni di query LINQ (Guida per programmatori C#)

Cenni preliminari sugli operatori di query standard

Altre risorse

LINQ (Language-Integrated Query)

Parole chiave di query (Riferimenti per C#)

Cronologia delle modifiche

Data

Cronologia

Motivo

Maggio 2010

È stato modificato l'output visualizzato per il primo esempio nella sezione "Variabile query".

Commenti e suggerimenti dei clienti.