Panoramica degli operatori di query standard
Gli operatori di query standard sono le parole chiave e i metodi che formano il modello LINQ. Il linguaggio C# definisce parole chiave di query LINQ che usi per le espressioni di interrogazione più comuni. Il compilatore traduce le espressioni che utilizzano queste parole chiave in chiamate di metodo equivalenti. Le due forme sono sinonimi. Altri metodi che fanno parte dello spazio dei nomi System.Linq non hanno parole chiave di query equivalenti. In questi casi, è necessario usare la sintassi del metodo . In questa sezione vengono illustrate tutte le parole chiave dell'operatore di query. Il runtime e altri pacchetti NuGet aggiungono altri metodi progettati per funzionare con le query LINQ ogni versione. I metodi più comuni, inclusi quelli con equivalenti di parole chiave di query, sono descritti in questa sezione. Per l'elenco completo dei metodi di query supportati dal runtime .NET, vedere la documentazione dell'API System.Linq.Enumerable. Oltre ai metodi descritti qui, questa classe contiene metodi per concatenare origini dati, calcolando un singolo valore da un'origine dati, ad esempio una somma, una media o un altro valore.
Importante
In questi esempi viene usata un'origine dati System.Collections.Generic.IEnumerable<T>. Le sorgenti di dati basate su System.Linq.IQueryProvider utilizzano sorgenti di dati System.Linq.IQueryable<T> e alberi di espressioni. La sintassi consentita in C# per gli alberi delle espressioni ha limitazioni. Inoltre, ogni origine dati IQueryProvider
, ad esempio EF Core può imporre altre restrizioni. Consultare la documentazione relativa all'origine dati.
La maggior parte di questi metodi opera sulle sequenze, in cui una sequenza è un oggetto il cui tipo implementa l'interfaccia IEnumerable<T> o l'interfaccia IQueryable<T>. Gli operatori di query standard offrono funzionalità di query, tra cui filtro, proiezione, aggregazione, ordinamento e altro ancora. I metodi che costituiscono ogni set sono rispettivamente membri statici delle classi Enumerable e Queryable. Vengono definiti come metodi di estensione del tipo su cui operano.
La distinzione tra IEnumerable<T> e IQueryable<T> sequenze determina la modalità di esecuzione della query in fase di esecuzione.
Per IEnumerable<T>
, l'oggetto enumerabile restituito acquisisce gli argomenti passati al metodo . Quando tale oggetto viene enumerato, viene utilizzata la logica dell'operatore di query e vengono restituiti i risultati della query.
Per IQueryable<T>
, la query viene convertita in un albero delle espressioni . L'albero delle espressioni può essere convertito in una query nativa quando l'origine dati può ottimizzare la query. Librerie come Entity Framework traducono le query LINQ in query SQL native che vengono eseguite nel database.
Nell'esempio di codice seguente viene illustrato come usare gli operatori di query standard per ottenere informazioni su una sequenza.
string sentence = "the quick brown fox jumps over the lazy dog";
// Split the string into individual words to create a collection.
string[] words = sentence.Split(' ');
// Using query expression syntax.
var query = from word in words
group word.ToUpper() by word.Length into gr
orderby gr.Key
select new { Length = gr.Key, Words = gr };
// Using method-based query syntax.
var query2 = words.
GroupBy(w => w.Length, w => w.ToUpper()).
Select(g => new { Length = g.Key, Words = g }).
OrderBy(o => o.Length);
foreach (var obj in query)
{
Console.WriteLine($"Words of length {obj.Length}:");
foreach (string word in obj.Words)
Console.WriteLine(word);
}
// This code example produces the following output:
//
// Words of length 3:
// THE
// FOX
// THE
// DOG
// Words of length 4:
// OVER
// LAZY
// Words of length 5:
// QUICK
// BROWN
// JUMPS
Se possibile, le query in questa sezione usano una sequenza di parole o numeri come origine di input. Per le query in cui vengono usate relazioni più complesse tra oggetti, vengono usate le origini seguenti che modellano un istituto di istruzione:
public enum GradeLevel
{
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};
public class Student
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required int ID { get; init; }
public required GradeLevel Year { get; init; }
public required List<int> Scores { get; init; }
public required int DepartmentID { get; init; }
}
public class Teacher
{
public required string First { get; init; }
public required string Last { get; init; }
public required int ID { get; init; }
public required string City { get; init; }
}
public class Department
{
public required string Name { get; init; }
public int ID { get; init; }
public required int TeacherID { get; init; }
}
Ogni Student
ha un livello di classe, un reparto primario e una serie di punteggi. Un Teacher
ha anche una proprietà City
che identifica il campus in cui l'insegnante tiene le lezioni. Un Department
ha un nome e un riferimento a un Teacher
che funge da responsabile del reparto.
È possibile trovare il set di dati nel repository di origine .
Tipi di operatori di query
Gli operatori di query standard variano in base alla tempistica dell'esecuzione, a seconda che restituiscano un valore singleton o una sequenza di valori. I metodi che restituiscono un valore singleton , ad esempio Average e Sum, vengono eseguiti immediatamente. I metodi che restituiscono una sequenza rinviano l'esecuzione della query e restituiscono un oggetto enumerabile. È possibile usare la sequenza di output di una query come sequenza di input in un'altra query. Le chiamate ai metodi di query possono essere concatenate in un'unica query, che consente alle query di diventare arbitrariamente complesse.
Operatori di query
In una query LINQ il primo passaggio consiste nel specificare l'origine dati. In una query LINQ, la clausola from
viene fornita per prima cosa per introdurre l'origine dati (students
) e la variabile di intervallo (student
).
//queryAllStudents is an IEnumerable<Student>
var queryAllStudents = from student in students
select student;
La variabile di intervallo è simile alla variabile di iterazione in un ciclo foreach
, ad eccezione del fatto che non si verifica alcuna iterazione effettiva in un'espressione di query. Quando viene eseguita la query, la variabile di intervallo funge da riferimento a ogni elemento successivo in students
. Poiché il compilatore può dedurre il tipo di student
, non è necessario specificarlo in modo esplicito. È possibile introdurre più variabili di intervallo in una clausola let
. Per ulteriori informazioni, vedere la clausola let tra e.
Nota
Per le origini dati non generica, ad esempio ArrayList, la variabile di intervallo deve essere tipizzata in modo esplicito. Per altre informazioni, vedere How to query an ArrayList with LINQ (C#) and from clause.
Dopo aver ottenuto un'origine dati, è possibile eseguire un numero qualsiasi di operazioni su tale origine dati:
-
Filtrare i dati usando la parola chiave
where
. -
Ordina i dati usando le parole chiave
orderby
e facoltativamentedescending
. -
raggruppare i dati usando le parole chiave
group
e facoltativamenteinto
. -
Unisci i dati con la parola chiave
join
. -
Dati del progetto usando la parola chiave
select
.
Tabella della sintassi delle espressioni di query
Nella tabella seguente sono elencati gli operatori di query standard con clausole di espressione di query equivalenti.
Metodo | Sintassi delle espressioni di query C# |
---|---|
Cast | Usare una variabile di intervallo tipizzata in modo esplicito:from int i in numbers Per altre informazioni, vedere nella clausola. |
GroupBy | group … by oppure group … by … into … Per ulteriori informazioni, vedere la clausola del gruppo . |
GroupJoin<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, Func<TOuter,IEnumerable<TInner>, TResult>) | join … in … on … equals … into … Per altre informazioni, vedere la clausola di join . |
Join<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, Func<TOuter,TInner,TResult>) | join … in … on … equals … Per altre informazioni, vedere clausola join.) |
OrderBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) | orderby Per maggiori informazioni, vedere la clausola di ordinamento . |
OrderByDescending<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) | orderby … descending Per altre informazioni, vedere clausola orderby.) |
Select | select Per ulteriori informazioni, vedere la clausola di selezione . |
SelectMany | Più clausole from .Per ulteriori informazioni, vedere nella clausola. |
ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>) | orderby …, … Per altre informazioni, vedere clausola orderby.) |
ThenByDescending<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>) | orderby …, … descending Per ulteriori informazioni, vedere la clausola di ordinamento . |
Where | where Per ulteriori informazioni, vedere la clausola dove in. |
Trasformazioni dei dati con LINQ
Language-Integrated query (LINQ) non riguarda solo il recupero dei dati. È anche uno strumento potente per trasformare i dati. Usando una query LINQ, è possibile usare una sequenza di origine come input e modificarla in molti modi per creare una nuova sequenza di output. È possibile modificare la sequenza stessa senza modificare gli elementi stessi ordinando e raggruppando. Ma forse la funzionalità più potente delle query LINQ è la possibilità di creare nuovi tipi. La clausola di selezione crea un elemento di output da un elemento di input. Viene usato per trasformare un elemento di input in un elemento di output:
- Unire più sequenze di input in una singola sequenza di output con un nuovo tipo.
- Creare sequenze di output i cui elementi sono costituiti da una o più proprietà di ogni elemento nella sequenza di origine.
- Creare sequenze di output i cui elementi sono costituiti dai risultati delle operazioni eseguite sui dati di origine.
- Creare sequenze di output in un formato diverso. Ad esempio, è possibile trasformare i dati da righe SQL o file di testo in XML.
Queste trasformazioni possono essere combinate in vari modi nella stessa query. Inoltre, la sequenza di output di una query può essere usata come sequenza di input per una nuova query. Nell'esempio seguente vengono trasformati oggetti in una struttura di dati in memoria in elementi XML.
// Create the query.
var studentsToXML = new XElement("Root",
from student in students
let scores = string.Join(",", student.Scores)
select new XElement("student",
new XElement("First", student.FirstName),
new XElement("Last", student.LastName),
new XElement("Scores", scores)
) // end "student"
); // end "Root"
// Execute the query.
Console.WriteLine(studentsToXML);
Il codice produce l'output XML seguente:
<Root>
<student>
<First>Svetlana</First>
<Last>Omelchenko</Last>
<Scores>97,90,73,54</Scores>
</student>
<student>
<First>Claire</First>
<Last>O'Donnell</Last>
<Scores>56,78,95,95</Scores>
</student>
...
<student>
<First>Max</First>
<Last>Lindgren</Last>
<Scores>86,88,96,63</Scores>
</student>
<student>
<First>Arina</First>
<Last>Ivanova</Last>
<Scores>93,63,70,80</Scores>
</student>
</Root>
Per altre informazioni, vedere Creazione di alberi XML in C# (LINQ to XML).
È possibile usare i risultati di una query come origine dati per una query successiva. In questo esempio viene illustrato come ordinare i risultati di un'operazione di join. Questa query crea un join di gruppo e quindi ordina i gruppi in base all'elemento categoria, che è ancora attivo. All'interno dell'inizializzatore di tipo anonimo, una sottoquery ordina tutti gli elementi corrispondenti della sequenza di prodotti.
var orderedQuery = from department in departments
join student in students on department.ID equals student.DepartmentID into studentGroup
orderby department.Name
select new
{
DepartmentName = department.Name,
Students = from student in studentGroup
orderby student.LastName
select student
};
foreach (var departmentList in orderedQuery)
{
Console.WriteLine(departmentList.DepartmentName);
foreach (var student in departmentList.Students)
{
Console.WriteLine($" {student.LastName,-10} {student.FirstName,-10}");
}
}
/* Output:
Chemistry
Balzan Josephine
Fakhouri Fadi
Popov Innocenty
Seleznyova Sofiya
Vella Carmen
Economics
Adams Terry
Adaobi Izuchukwu
Berggren Jeanette
Garcia Cesar
Ifeoma Nwanneka
Jamuike Ifeanacho
Larsson Naima
Svensson Noel
Ugomma Ifunanya
Engineering
Axelsson Erik
Berg Veronika
Engström Nancy
Hicks Cassie
Keever Bruce
Micallef Nicholas
Mortensen Sven
Nilsson Erna
Tucker Michael
Yermolayeva Anna
English
Andersson Sarah
Feng Hanying
Ivanova Arina
Jakobsson Jesper
Jensen Christiane
Johansson Mark
Kolpakova Nadezhda
Omelchenko Svetlana
Urquhart Donald
Mathematics
Frost Gaby
Garcia Hugo
Hedlund Anna
Kovaleva Katerina
Lindgren Max
Maslova Evgeniya
Olsson Ruth
Sammut Maria
Sazonova Anastasiya
Physics
Åkesson Sami
Edwards Amy E.
Falzon John
Garcia Debra
Hansson Sanna
Mattsson Martina
Richardson Don
Zabokritski Eugene
*/
La query equivalente tramite la sintassi del metodo è illustrata nel codice seguente:
var orderedQuery = departments
.GroupJoin(students, department => department.ID, student => student.DepartmentID,
(department, studentGroup) => new
{
DepartmentName = department.Name,
Students = studentGroup.OrderBy(student => student.LastName)
})
.OrderBy(department => department.DepartmentName);
foreach (var departmentList in orderedQuery)
{
Console.WriteLine(departmentList.DepartmentName);
foreach (var student in departmentList.Students)
{
Console.WriteLine($" {student.LastName,-10} {student.FirstName,-10}");
}
}
Sebbene sia possibile usare una clausola orderby
con una o più sequenze di origine prima del join, in genere non è consigliabile. Alcuni provider LINQ potrebbero non conservare l'ordinamento dopo il join. Per ulteriori informazioni, vedere la clausola join .