Condividi tramite


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:

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 .

Vedere anche