Condividi tramite


Introduzione alle query LINQ in C#

Una query è un'espressione che recupera i dati da un'origine dati. Origini dati diverse hanno linguaggi di query nativi diversi, ad esempio SQL per database relazionali e XQuery per XML. Gli sviluppatori devono apprendere un nuovo linguaggio di query per ogni tipo di origine dati o formato di dati che devono supportare. LINQ semplifica questa situazione offrendo un modello di linguaggio C# coerente per tipi di origini dati e formati. In una query LINQ si lavora sempre con oggetti C#. Si usano gli stessi modelli di codifica di base per eseguire query e trasformare i dati in documenti XML, database SQL, raccolte .NET e qualsiasi altro formato quando è disponibile un provider LINQ.

Tre parti di un'operazione di interrogazione

Tutte le operazioni di query LINQ sono costituite da tre azioni distinte:

  1. Ottenere l'origine dati.
  2. Creare la query.
  3. Esecuzione della query.

Nell'esempio seguente viene illustrato come le tre parti di un'operazione di query vengono espresse nel codice sorgente. Nell'esempio viene usata una matrice integer come origine dati per praticità; Tuttavia, gli stessi concetti si applicano anche ad altre origini dati. Questo esempio è citato nel resto dell'articolo.

// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery = from num in numbers
               where (num % 2) == 0
               select num;

// 3. Query execution.
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

La figura seguente mostra l'operazione di query completa. In LINQ l'esecuzione della query è distinta dalla query stessa. In altre parole, non si recuperano dati creando una variabile di query.

Diagramma dell'intera operazione di query LINQ.

La origine dati

L'origine dati nell'esempio precedente è una matrice che supporta l'interfaccia generica IEnumerable<T> . Questo significa che può essere interrogato con LINQ. Una query viene eseguita in un'istruzione foreach e foreach richiede IEnumerable o IEnumerable<T>. I tipi che supportano IEnumerable<T> o un'interfaccia derivata, ad esempio il generico IQueryable<T> , sono denominati tipi queryable.

Un tipo interrogabile non richiede alcuna modifica o elaborazione speciale per fungere da origine dati LINQ. Se i dati di origine non sono già in memoria come tipo queryable, il provider LINQ deve rappresentarlo come tale. Ad esempio, LINQ to XML carica un documento XML in un tipo queryable XElement :

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

Con EntityFramework si crea un mapping relazionale a oggetti tra le classi C# e lo schema del database. Le query vengono scritte sugli oggetti e in fase di esecuzione EntityFramework gestisce la comunicazione con il database. Nell'esempio seguente rappresenta Customers una tabella specifica nel database e il tipo del risultato della query, IQueryable<T>, deriva da IEnumerable<T>.

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

Per altre informazioni su come creare tipi specifici di origini dati, vedere la documentazione per i vari provider LINQ. Tuttavia, la regola di base è semplice: un'origine dati LINQ è qualsiasi oggetto che supporta l'interfaccia generica IEnumerable<T> o un'interfaccia che eredita da essa, in genere IQueryable<T>.

Annotazioni

I tipi come ArrayList quelli che supportano l'interfaccia non generica IEnumerable possono essere usati anche come origine dati LINQ. Per altre informazioni, vedere Come eseguire query su un arrayList con LINQ (C#).

The Query

La query specifica quali informazioni ottenere dalla fonte o dalle fonti dati. Facoltativamente, una query specifica anche la modalità di ordinamento, raggruppamento e forma delle informazioni prima di essere restituite. Una query viene archiviata in una variabile di query e inizializzata con un'espressione di query. Si usa la sintassi di query C# per scrivere query.

La query nell'esempio precedente restituisce tutti i numeri pari della matrice integer. L'espressione di query contiene tre clausole: from, wheree select. Se si ha familiarità con SQL, si è notato che l'ordinamento delle clausole viene invertito dall'ordine in SQL. La from clausola specifica l'origine dati, la where clausola applica il filtro e la select clausola specifica il tipo degli elementi restituiti. Tutte le clausole di query sono trattate in dettaglio in questa sezione. Per il momento, il punto importante è che in LINQ la variabile di query stessa non esegue alcuna azione e non restituisce dati. Archivia solo le informazioni necessarie per produrre i risultati quando la query viene eseguita in un secondo momento. Per altre informazioni sulla modalità di costruzione delle query, vedere Panoramica degli operatori di query standard (C#).

Annotazioni

Le query possono essere espresse anche usando la sintassi del metodo . Per altre informazioni, vedere la sintassi di query e la sintassi dei metodi in LINQ.

Classificazione degli operatori di query standard in base alla modalità di esecuzione

Le implementazioni LINQ to Objects dei metodi dell'operatore di query standard vengono eseguite in uno dei due modi principali: immediato o posticipato. Gli operatori di query che usano l'esecuzione posticipata possono essere suddivisi in due categorie: streaming e nonstreaming.

Immediato

L'esecuzione immediata indica che l'origine dati viene letta e l'operazione viene eseguita una sola volta. Tutti gli operatori di query standard che eseguono immediatamente restituiscono un risultato scalare. Esempi di tali query sono Count, Max, Averagee First. Questi metodi vengono eseguiti senza un'istruzione esplicita foreach perché la query stessa deve essere usata foreach per restituire un risultato. Queste query restituiscono un singolo valore, non una IEnumerable raccolta. È possibile forzare qualsiasi query a eseguire immediatamente utilizzando i metodi Enumerable.ToList o Enumerable.ToArray. L'esecuzione immediata fornisce il riutilizzo dei risultati della query, non della dichiarazione di query. I risultati vengono recuperati una sola volta, quindi archiviati per un uso futuro. La query seguente restituisce un conteggio dei numeri pari nella matrice di origine:

var evenNumQuery = from num in numbers
                   where (num % 2) == 0
                   select num;

int evenNumCount = evenNumQuery.Count();

Per forzare l'esecuzione immediata di qualsiasi query e memorizzarne nella cache i risultati, è possibile chiamare i ToList metodi o ToArray .

List<int> numQuery2 = (from num in numbers
                       where (num % 2) == 0
                       select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 = (from num in numbers
                 where (num % 2) == 0
                 select num).ToArray();

È anche possibile forzare l'esecuzione inserendo il foreach ciclo immediatamente dopo l'espressione di query. Tuttavia, chiamando ToList o ToArray si memorizzano nella cache anche tutti i dati in un singolo oggetto raccolta.

Differito

L'esecuzione posticipata indica che l'operazione non viene eseguita nel punto nel codice in cui viene dichiarata la query. L'operazione viene eseguita solo quando la variabile di query viene enumerata, ad esempio usando un'istruzione foreach . I risultati dell'esecuzione della query dipendono dal contenuto dell'origine dati quando la query viene eseguita anziché da quando viene definita la query. Se la variabile di query viene enumerata più volte, i risultati possono variare ogni volta. Quasi tutti gli operatori di query standard il cui tipo restituito è IEnumerable<T> o IOrderedEnumerable<TElement> vengono eseguiti in modo posticipato. L'esecuzione posticipata offre la funzionalità di riutilizzo delle query perché la query recupera i dati aggiornati dall'origine dati ogni volta che i risultati delle query vengono iterati. Il codice seguente illustra un esempio di esecuzione posticipata:

foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

L'istruzione foreach è anche la posizione in cui vengono recuperati i risultati della query. Nella query precedente, ad esempio, la variabile num di iterazione contiene ogni valore (uno alla volta) nella sequenza restituita.

Poiché la variabile di query stessa non contiene mai i risultati della query, è possibile eseguirla ripetutamente per recuperare i dati aggiornati. Ad esempio, un'applicazione separata potrebbe aggiornare continuamente un database. Nell'applicazione è possibile creare una query che recupera i dati più recenti ed è possibile eseguirla a intervalli per recuperare i risultati aggiornati.

Gli operatori di query che utilizzano l'esecuzione differita possono essere ulteriormente classificati come streaming o nonstreaming.

Trasmissione in diretta

Gli operatori di streaming non devono leggere tutti i dati di origine prima di produrre elementi. Al momento dell'esecuzione, un operatore di streaming esegue l'operazione su ogni elemento di origine durante la lettura e restituisce l'elemento, se appropriato. Un operatore di streaming continua a leggere gli elementi di origine fino a quando non è possibile produrre un elemento risultato. Ciò significa che più di un elemento di origine potrebbero essere letti per produrre un elemento risultato.

Nonstreaming

Gli operatori non di flusso devono leggere tutti i dati di origine prima di poter restituire un elemento risultato. Le operazioni come l'ordinamento o il raggruppamento rientrano in questa categoria. Al momento dell'esecuzione, gli operatori di query non di flusso leggono tutti i dati di origine, li inseriscono in una struttura di dati, eseguono l'operazione e producono gli elementi risultanti.

Tabella di classificazione

La tabella seguente classifica ogni metodo dell'operatore di query standard in base al relativo metodo di esecuzione.

Annotazioni

Se un operatore è contrassegnato in due colonne, due sequenze di input sono coinvolte nell'operazione e ogni sequenza viene valutata in modo diverso. In questi casi, è sempre la prima sequenza nell'elenco di parametri che viene valutata in modo posticipato e in streaming.

Operatore di interrogazione standard Tipo di ritorno Esecuzione immediata Esecuzione posticipata dello streaming Esecuzione differita non in streaming
Aggregate TSource
All Boolean
Any Boolean
AsEnumerable IEnumerable<T>
Average Valore numerico singolo
Cast IEnumerable<T>
Concat IEnumerable<T>
Contains Boolean
Count Int32
DefaultIfEmpty IEnumerable<T>
Distinct IEnumerable<T>
ElementAt TSource
ElementAtOrDefault TSource?
Empty IEnumerable<T>
Except IEnumerable<T>
First TSource
FirstOrDefault TSource?
GroupBy IEnumerable<T>
GroupJoin IEnumerable<T>
Intersect IEnumerable<T>
Join IEnumerable<T>
Last TSource
LastOrDefault TSource?
LongCount Int64
Max Valore numerico singolo, TSource, o TResult?
Min Valore numerico singolo, TSource, o TResult?
OfType IEnumerable<T>
OrderBy IOrderedEnumerable<TElement>
OrderByDescending IOrderedEnumerable<TElement>
Range IEnumerable<T>
Repeat IEnumerable<T>
Reverse IEnumerable<T>
Select IEnumerable<T>
SelectMany IEnumerable<T>
SequenceEqual Boolean
Single TSource
SingleOrDefault TSource?
Skip IEnumerable<T>
SkipWhile IEnumerable<T>
Sum Valore numerico singolo
Take IEnumerable<T>
TakeWhile IEnumerable<T>
ThenBy IOrderedEnumerable<TElement>
ThenByDescending IOrderedEnumerable<TElement>
ToArray TSource[] array
ToDictionary Dictionary<TKey,TValue>
ToList IList<T>
ToLookup ILookup<TKey,TElement>
Union IEnumerable<T>
Where IEnumerable<T>

LINQ per oggetti

"LINQ to Objects" si riferisce all'uso di query LINQ direttamente con qualsiasi IEnumerable collezione o IEnumerable<T> collezione. È possibile usare LINQ per eseguire query su qualsiasi raccolta enumerabile, ad esempio List<T>, Arrayo Dictionary<TKey,TValue>. La raccolta può essere definita dall'utente o un tipo restituito da un'API .NET. Nell'approccio LINQ si scrive codice dichiarativo che descrive ciò che si vuole recuperare. LINQ to Objects offre un'ottima introduzione alla programmazione con LINQ.

Le query LINQ offrono tre vantaggi principali rispetto ai cicli tradizionali foreach :

  • Sono più concisi e leggibili, soprattutto quando si filtrano più condizioni.
  • Offrono funzionalità avanzate di filtro, ordinamento e raggruppamento con un minimo di codice dell'applicazione.
  • Possono essere trasferiti in altre origini dati con poche o nessuna modifica.

Più complessa è l'operazione che si vuole eseguire sui dati, maggiore è il vantaggio che si realizza usando LINQ invece delle tecniche di iterazione tradizionali.

Archiviare i risultati di una query in memoria

Una query è fondamentalmente un set di istruzioni per recuperare e organizzare i dati. Le query vengono eseguite in modo pigro, mentre viene richiesto ogni elemento successivo nel risultato. Quando si usa foreach per scorrere i risultati, gli elementi vengono restituiti come accessibili. Per valutare una query e archiviare i risultati senza eseguire un foreach ciclo, è sufficiente chiamare uno dei metodi seguenti nella variabile di query:

È necessario assegnare l'oggetto raccolta restituito a una nuova variabile quando si archiviano i risultati della query, come illustrato nell'esempio seguente:

List<int> numbers = [ 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 ];

IEnumerable<int> queryFactorsOfFour = from num in numbers
                                      where num % 4 == 0
                                      select num;

// Store the results in a new variable
// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();

// Read and write from the newly created list to demonstrate that it holds data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

Vedere anche