Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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:
- Ottenere l'origine dati.
- Creare la query.
- 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.
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
, where
e 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
, Average
e 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.
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]);