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 unint[]
):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 unstring
. Si noti il nuovo tipo dihighScoresQuery
.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
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 select
proietta 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.