Condividi tramite


Nozioni di base sulle espressioni di query

Questo articolo presenta i concetti di base correlati alle espressioni di query in C#.

Che cos'è una query e che cosa fa?

Una query è un insieme di istruzioni che descrive quali dati recuperare da una fonte di dati specifica (o fonti) e quale forma e organizzazione devono avere i dati restituiti. Una query è distinta dai risultati che essa produce.

In genere, i dati di origine sono organizzati logicamente come sequenza di elementi dello stesso tipo. Ad esempio, una tabella di database SQL contiene una sequenza di righe. In un file XML è presente una "sequenza" di elementi XML (anche se gli elementi XML sono organizzati gerarchicamente in una struttura ad albero). Una raccolta in memoria contiene una sequenza di oggetti.

Dal punto di vista di un'applicazione, il tipo e la struttura specifici dei dati di origine originali non sono importanti. L'applicazione vede sempre i dati di origine come raccolta IEnumerable<T> o IQueryable<T>. Ad esempio, in LINQ to XML i dati di origine sono resi visibili come IEnumerable<XElement>.

Data questa sequenza di origine, una query potrebbe eseguire una delle tre operazioni seguenti:

  • Recuperare un subset degli elementi per produrre una nuova sequenza senza modificare i singoli elementi. La query potrebbe quindi ordinare o raggruppare la sequenza restituita in vari modi, come illustrato nell'esempio seguente (si supponga che scores sia un 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. Ad esempio, una query potrebbe recuperare solo i cognomi da determinati record di clienti in una fonte di dati. Oppure potrebbe recuperare il record completo e usarlo per costruire un altro tipo di oggetto in memoria o anche dati XML prima di generare la sequenza di risultati finale. Nell'esempio seguente viene illustrata una proiezione da un int a un string. Si noti il nuovo tipo di highScoresQuery.

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

    • Numero di elementi che corrispondono a una determinata condizione.

    • Elemento con il valore massimo o minimo.

    • Primo elemento che corrisponde a una condizione o alla somma di valori specifici in un set specificato di elementi. Ad esempio, la query seguente restituisce il numero di punteggi maggiori di 80 dalla matrice integer scores:

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

      Nell'esempio precedente si noti l'uso di parentesi intorno all'espressione di query prima della chiamata al metodo Enumerable.Count. È anche possibile usare una nuova variabile per archiviare il risultato concreto.

      IEnumerable<int> highScoresQuery3 =
          from score in scores
          where score > 80
          select score;
      
      var 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.

Che cos'è un'espressione di query?

Un'espressione di query è una query espressa utilizzando la sintassi di query. Un'espressione di query è un costrutto di linguaggio di prima classe. È come qualsiasi altra espressione e può essere usata in qualsiasi contesto in cui un'espressione C# è valida. Un'espressione di query è costituita da un set di clausole scritte in una sintassi dichiarativa simile a SQL o XQuery. Ogni clausola, a sua volta, contiene una o più espressioni C# e queste espressioni possono essere esse stesse un'espressione di query oppure contenere un'espressione di query.

Un'espressione di query deve iniziare con una clausola e terminare con una clausola di selezione o una clausola di raggruppamento . Tra la prima clausola from e l'ultima clausola select o group, può contenere una o più di queste clausole facoltative: dove, orderby, join, consentire e persino un altro dalle clausole di. È anche possibile usare il nella parola chiave affinché il risultato di una clausola join o group possa fungere da origine per altre clausole nella stessa espressione.

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 produce una sequenza di elementi quando viene eseguita l'iterazione in un'istruzione foreach o una chiamata diretta al relativo metodo IEnumerator.MoveNext().

Nota

Gli esempi in questo articolo si avvalgono dei seguenti dati di origine e di esempio.

record City(string Name, long Population);
record Country(string Name, double Area, long Population, List<City> Cities);
record Product(string Name, string Category);
static readonly City[] cities = [
    new City("Tokyo", 37_833_000),
    new City("Delhi", 30_290_000),
    new City("Shanghai", 27_110_000),
    new City("São Paulo", 22_043_000),
    new City("Mumbai", 20_412_000),
    new City("Beijing", 20_384_000),
    new City("Cairo", 18_772_000),
    new City("Dhaka", 17_598_000),
    new City("Osaka", 19_281_000),
    new City("New York-Newark", 18_604_000),
    new City("Karachi", 16_094_000),
    new City("Chongqing", 15_872_000),
    new City("Istanbul", 15_029_000),
    new City("Buenos Aires", 15_024_000),
    new City("Kolkata", 14_850_000),
    new City("Lagos", 14_368_000),
    new City("Kinshasa", 14_342_000),
    new City("Manila", 13_923_000),
    new City("Rio de Janeiro", 13_374_000),
    new City("Tianjin", 13_215_000)
];

static readonly Country[] countries = [
    new Country ("Vatican City", 0.44, 526, [new City("Vatican City", 826)]),
    new Country ("Monaco", 2.02, 38_000, [new City("Monte Carlo", 38_000)]),
    new Country ("Nauru", 21, 10_900, [new City("Yaren", 1_100)]),
    new Country ("Tuvalu", 26, 11_600, [new City("Funafuti", 6_200)]),
    new Country ("San Marino", 61, 33_900, [new City("San Marino", 4_500)]),
    new Country ("Liechtenstein", 160, 38_000, [new City("Vaduz", 5_200)]),
    new Country ("Marshall Islands", 181, 58_000, [new City("Majuro", 28_000)]),
    new Country ("Saint Kitts & Nevis", 261, 53_000, [new City("Basseterre", 13_000)])
];

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

// 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 (var testScore in scoreQuery)
{
    Console.WriteLine(testScore);
}

// Output: 93 90 82 82

Nell'esempio precedente, scoreQuery è una variabile di query , a volte indicata semplicemente come una query . La variabile di query non archivia dati di risultato effettivi, generati nel ciclo foreach. Quando viene eseguita l'istruzione foreach, i risultati della query non vengono restituiti tramite la variabile di query scoreQuery. Vengono invece restituiti tramite la variabile di iterazione testScore. La variabile scoreQuery può essere iterata in un secondo ciclo foreach. Produce gli stessi risultati finché né esso né l'origine dati siano stati modificati.

Una variabile di query potrebbe archiviare una query espressa nella sintassi di query o nella sintassi del metodo o in una combinazione dei due. Negli esempi seguenti, sia queryMajorCities che queryMajorCities2 sono variabili di query:

City[] cities = [
    new City("Tokyo", 37_833_000),
    new City("Delhi", 30_290_000),
    new City("Shanghai", 27_110_000),
    new City("São Paulo", 22_043_000)
];

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

// Execute the query to produce the results
foreach (City city in queryMajorCities)
{
    Console.WriteLine(city);
}

// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }

// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 30_000_000);
// Execute the query to produce the results
foreach (City city in queryMajorCities2)
{
    Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }

D'altra parte, i due esempi seguenti mostrano variabili che non sono variabili di query anche se ognuna viene inizializzata con una query. Non sono variabili di query perché archiviano i risultati:

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

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

var highScore = scoreQuery.Max();
// the following returns the same result
highScore = scores.Max();
var 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;
var largeCitiesList2 = largeCitiesQuery.ToList();

Tipizzazione esplicita e implicita delle variabili di query

Questa documentazione fornisce in genere il tipo esplicito della variabile di query per visualizzare la relazione di tipo tra la variabile di query e la clausola select . Tuttavia, è anche possibile usare la parola chiave var var per indicare al compilatore di dedurre il tipo di una variabile di query (o qualsiasi altra variabile locale) in fase di compilazione. Ad esempio, l'esempio di query illustrato in precedenza in questo articolo può essere espresso anche usando la digitazione implicita:

var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

Nell'esempio precedente l'uso di var è facoltativo. queryCities è un IEnumerable<City> se tipizzato in modo implicito o esplicito.

Inizio di un'espressione di query

Un'espressione di query deve iniziare con una clausola from. Specifica un'origine dati insieme a una variabile di intervallo. La variabile di intervallo rappresenta ogni elemento successivo nella sequenza di origine durante l'attraversamento della sequenza di origine. La variabile di intervallo è fortemente tipizzata in base al tipo di elementi nell'origine dati. Nell'esempio seguente, poiché countries è una matrice di oggetti Country, la variabile di intervallo viene anche digitata come Country. Poiché la variabile di intervallo è fortemente tipizzata, è possibile usare l'operatore dot per accedere a tutti i membri disponibili del tipo.

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

La variabile di intervallo è nell'ambito fino a quando la query non viene chiusa con un punto e virgola o con una clausola di continuazione.

Un'espressione di query può contenere più clausole di from. Usare più clausole from quando ogni elemento della sequenza di origine è una collezione o contiene una collezione. Si supponga, ad esempio, di avere una raccolta di oggetti Country, ognuno dei quali contiene una raccolta di oggetti City denominati Cities. Per eseguire una query sugli oggetti City in ogni Country, usare 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 altre informazioni, vedere dalla clausola.

Termina un'espressione di query

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

La clausola di gruppo

Utilizzare la clausola group per produrre una sequenza di gruppi organizzati in base a una chiave specificata. La chiave può essere qualsiasi tipo di dati. Ad esempio, la query seguente crea una sequenza di gruppi che contiene uno o più oggetti Country e la cui chiave è un tipo char con valore corrispondente alla prima lettera dei nomi dei paesi.

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

Per altre informazioni sul raggruppamento, vedere clausola di gruppo.

clausola SELECT

Utilizzare la clausola select per produrre tutti gli altri tipi di sequenze. Una semplice clausola select genera una sequenza dello stesso tipo degli oggetti contenuti nell'origine dati. In questo esempio l'origine dati contiene Country oggetti . La clausola orderby ordina semplicemente gli elementi in un nuovo ordine 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 usata per trasformare i dati di origine in sequenze di nuovi tipi. Questa trasformazione è denominata anche proiezione . Nell'esempio seguente, la clausola selectproietta una sequenza di tipi anonimi che contiene solo un sottoinsieme dei campi nell'elemento originale. I nuovi oggetti vengono inizializzati utilizzando un inizializzatore di oggetto.

var queryNameAndPop =
    from country in countries
    select new
    {
        Name = country.Name,
        Pop = country.Population
    };

Pertanto, in questo esempio, la var è necessaria perché la query produce un tipo anonimo.

Per ulteriori informazioni su tutti i modi in cui è possibile utilizzare una clausola select per trasformare i dati di origine, vedere la select clause .

Continuazioni con in

È possibile usare la parola chiave into in una clausola select o group per creare un identificatore temporaneo che archivia una query. Usare la clausola into quando è necessario eseguire operazioni di query aggiuntive su una query dopo un'operazione di raggruppamento o selezione. Nell'esempio seguente, gli elementi indicati con countries sono raggruppati in base alla popolazione in intervalli di 10 milioni. Dopo aver creato questi gruppi, più clausole filtrano alcuni gruppi, e poi i gruppi vengono ordinati 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 / 1_000
    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 altre informazioni, vedere in.

Applicazione di filtri, ordinamento e join

Tra la clausola from iniziale e la clausola select o group finale, tutte le altre clausole (where, join, orderby, from, let) sono facoltative. Una delle clausole facoltative può essere usata zero volte o più volte in un corpo della query.

Clausola di condizione

Usare la clausola where per escludere gli elementi dai dati di origine in base a una o più espressioni di predicato. La clausola where nell'esempio seguente ha un predicato con due condizioni.

IEnumerable<City> queryCityPop =
    from city in cities
    where city.Population is < 15_000_000 and > 10_000_000
    select city;

Per ulteriori informazioni, vedere la clausola n.di .

Clausola di ordinamento

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

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

La parola chiave ascending è facoltativa; è l'ordinamento predefinito se non viene specificato alcun ordine. Per altre informazioni, vedere clausola orderby.

Clausola join

Usare la clausola join per associare e/o combinare elementi di un'origine dati con 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 aver unito due sequenze, è necessario usare un'istruzione select o group per specificare l'elemento da archiviare nella sequenza di output. È anche possibile usare un tipo anonimo per combinare le proprietà di ogni set di elementi associati in un nuovo tipo per la sequenza di output. Nell'esempio seguente vengono associati prod oggetti la cui proprietà Category corrisponde a una delle categorie nella matrice di stringhe categories. I prodotti i cui Category non corrispondono ad alcuna stringa in categories vengono filtrati. L'istruzione select proietta un nuovo tipo le cui proprietà vengono ricavate 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 join di gruppo archiviando i risultati dell'operazione di join in una variabile temporanea usando il nella parola chiave. Per ulteriori informazioni, vedere la clausola join .

Clausola let

Usare 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 restituite da Split.

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

foreach (var s in queryFirstNames)
{
    Console.Write(s + " ");
}

//Output: Svetlana Claire Sven Cesar

Per ulteriori informazioni, vedere la clausola let tra e.

Sottoquery in un'espressione di query

Una clausola di query può contenere un'espressione di query, che talvolta viene definita una sottoquery. Ogni sottoquery inizia con la propria clausola from che non si riferisce necessariamente alla stessa origine dati della prima clausola from. Ad esempio, la query seguente mostra un'espressione di query usata nell'istruzione select per recuperare i risultati di un'operazione di raggruppamento.

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

Per altre informazioni, vedere eseguire una sottoquery in un'operazione di raggruppamento.

Vedere anche