Language INtegrated Query
LINQ fornisce uno strato di astrazione che rendono possibile l’accesso a diverse sorgente dati, senza preoccuparsi dei dettagli implementativi di ognuna di esse. Argomenti descritti precedentemente (variabili implicite, tipi anonimi, espressioni lamda) saranno la base fondamentale nella creazione ed esecuzione delle query LINQ.
Le espressioni sono scritte con un’apposita sintassi di query dichiarativa (query syntax), oppure mediante una serie di metodi di estensioni o operatori di query standard (method syntax). Le due sintassi sono combinabili in una singola query.
Ecco un esempio di query syntax che estrae il quadrato di tutti i numeri pari presenti in un array
Ora vediamo lo stesso esempio ma con il method syntax abc, in questo esempio utilizziamo la dichiarazione di tipo implicito
Da questi esempi si capisce che queste sono semplici dichiarazioni di quello che la query dovrà fare. Gli operatori di query sono implementati tramite i metodi di estensione delle classi statiche Enumerable e IQueryable.
La sintassi completa di una query LINQ ha il seguente formato
Supponiamo di avere dei dati relativi ai teams di formula uno, una banale query che ci restituisce tutti gli ogetti è
Questo esempio di query eseguito su un oggetto IEnumerable non tipizzato
Una query che estrae solo il nome di tutti i teams è
Scriviamo una query che estrae i cognomi dei piloti che fanno parte di un team con almeno due piloti e che il cognome sia più lungo di 8 caratteri
Dentro una query potremmo aver bisogno di un risultato di una sottoespressione, la parola chiave let permette di creare una variabile di range e inizializzarla per poi essere utilizzata.
Supponiamo di voler estrarre i teams ordinati con le vittorie vinte
Supponiamo di voler estrarre tutti i tems, ordine decrescente per punti, che hanno un pilota con più di 50 punti
Da questi esempi possiamo vedere che le query permettono di creare oggetti e memorizzare all’interno il risultato della query stessa. Ad esempio possiamo creare un oggetto Tuple formato dal nome della squadra e il numero di vittorie
Una seconda opzione è istanziare un oggetto anonimo impostando campi e proprietà pubbliche
Possiamo realizzare la stessa cosa utilizzando l’espressioni lambda
Immaginiamo di voler enumerare una sequenza a partire da ogni elemento di un’altra sequenza, e quindi ottenere un’unica collezione dall’unione delle due. Possiamo realizzare il tutto mediante clausole from composte oppure con l’operatore SelectMany
Dagli esempi visti notiamo che lo scopo delle ‘interrogazioni è quello restringere il numero di elementi della sorgente in base a filtri. Non tutti i filtri possono essere espressi mediante sintassi query, in casi più avanzati bisognerà ricorrere ai metodi di estensione che permettono di usare delegate ed espressioni lambda.
Possiamo ordinare i risultati, tramite una o più chiavi specificate, ottenuti tramite orderby, il valore predefinito è quello crescente (ascending) oppure specificare decrescente tramite descending. Possiamo ordinare i risultati tramite i metodi ThenBy e ThenByDescending. Mentre il metodo Reverse consente di invertire il risultato.
Possiamo decidere di raggruppare gli elementi di una sequenza secondo una particolare chiave tramite la clausola group … by. Possiamo estrarre tutti i piloti raggruppati per team
L’interfaccia dell’ogetto gruppo è IGrouping<K, V>, dove K rappresenta la chiave mentre V è il tipo degli elementi contenuti. Nel nostro esempio possiamo visualizzare i risultati
Il risultato di una query può essere passata ad una successiva mediante la clausola into. Supponiamo di voler estrarre tutti i piloti che fanno parte di un team con almeno due vittorie, di questi piloti ne straiamo solo quelli che hanno almeno 50 punti
Tramite la clausola join possiamo creare una sequenza a partire da due collezioni, accoppiando un elemento della prima con uno della seconda. Nell’esempio possiamo estrarre per ogni pilota il nome della relativa nazione
Vediamo ora come utilizzare due join, il secondo tramite la clausola into, oppure il metodo joingroup. Proviamo ad estrarre una collezione di nazioni, a ciacuna delle quali corrisponde una collezione di piloti.
Possiamo gestire i dati come insieme di elementi e dalla loro elaborazione ottenere i risultati richiesti, in questi esempi usiamo
- Distinct – restituisce gli elementi presenti nell’insiemi eliminando i duplicati;
- Union – unisce due insiemi eliminando le duplicazioni;
- Except – restituisce gli elementi del primo insieme che non sono presenti nel secondo;
- Intersect – restituisce gli elementi che fanno parte dei due insiemi;
- Zip – restituisce un nuovo insieme applicando una funzione ad ogni coppia di elementi;
- Skip – restituisce gli elementi saltando i primi elementi indicati;
- SkipWhile – restituisce gli elementi a partire da una determinata condizione;
- Take – restituisce i primi elementi indicati;
- TakeWhile – restituisce i primi elementi fino ad una determinata condizione;
Tramite gli operatori quantificatori possiamo verificare l’esistenza di elementi:
- Contains – verifica se la sequenza contiene un particolare elemento;
- All – verifica se tutti gli elementi di una sequenza soddisfano una condizione;
- Any – verifica che almeno un elemento soddisfa la condizione;
Possiamo combinare diversi metodi per creare query articolate; il primo esempio restituisce i team che hanno i loro piloti con punteggio maggiore di 18, mentre il secondo i team che hanno almeno un pilota con un punteggio maggiore di 70.
Possiamo recuperare il primo elemento con First, con Last recuperiamo l’ultimo elemento mentre Single recupera l’unico elemento. Se vogliamo ricevere eventualmente un valore di default usiamo ElementAtOrDefault, FirstAtOrDefault, LastAtOrDefault, SingleAtOrDefault.
Possiamo contare gli elementi di una sequenza tramite Count e LongCount, tutti senza nessun parametro oppure quelli che soddisfano la condizione. Possiamo fare la somma dei valori di una sequenza, ad esempio possiamo per ogni team sommare i punti dei relativi piloti
Possiamo calcolare la media tramite Average, oppure il minimo Min o il massimo Max di una sequenza. Tramite MinBy e MaxBy possiamo ottenere un oggetto complesso da una sequenza. In questo esempio otteniamo i piloti al primo e all’ultimo posto
Aggregate consente di specificare un’operazione personalizzata di accumulo, ad esempio possiamo ottenere per ogni team la somma dei punti di tutti i piloti di un team
Gli operatori ToArray, ToList, ToDictionary e ToLookup permettono di effettuare una conversione di tipo.
- ToList – una lista di team che iniziano per M
List<F1Team> result = teams.Where(t => t.TeamName.ToUpper().StartsWith(“M”)).ToList(); - ToArray – un array di piloti
Pilot[] array = teams.SelectMany(t => t.Pilots).ToArray(); - ToDictionary – crea un’istanza di classe Dictionary; il primo (il nome del team) è la chiave, mentre il secondo è un array di piloti
Il metodo ToLookup crea un’enumerazione, una sorta di dizionario costituito da una sequenza di elementi chiave ai quali corrispondono altre sequenze di elementi. L’esempio precedente può essere replicato come segue:
AsEnumerable restituisce la sequenza iniziale sotto forma di un oggetto IEnumerable<T>. OfType restituisce solo gli elementi di un dato tipo, mentre Cast permette di convertireun’enumerazione di elementi in un differente tipo. Tramite Concat possiamo concatenare due sequenze una dopo l’altra, mentre SequenceEqual verifica se due sequenze sono perfettamente uguali.