Nozioni fondamentali sulle espressioni di query (Guida per programmatori C#)
Aggiornamento: novembre 2007
Definizione e scopo di una query
Una query è un insieme 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 ADO.NETDataTable 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 sottoinsieme 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 insieme 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 insieme 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: 90 82 93 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();
IEnumerable<City> largeCityList =
(from country in countries
from city in country.Cities
where city.Population > 10000
select city)
.ToList();
// or split the expression
IEnumerable<City> largeCityList2 =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
List<City> largeCities = largeCityList2.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 Sintassi delle query e sintassi dei metodi (LINQ).
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).
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 stringa.
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 sottoinsieme 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 > 500000, 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 insieme 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 s 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 la classe 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
Espressioni query LINQ (Guida per programmatori C#)
Riferimenti
Guida generale per programmatori LINQ