C#Programmazione

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

IEnumerable<int> query = from n in array where n % 2 == 0 select n * n;

Ora vediamo lo stesso esempio ma con il method syntax abc, in questo esempio utilizziamo la dichiarazione di tipo implicito

var query = array.Where(n => n % 2 == 0).Select(n => n * n);

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

from [tipo] identificatore in espressione [clausole-query-body] clausola select | clausola group [continuazione-query]

Supponiamo di avere dei dati relativi ai teams di formula uno, una banale query che ci restituisce tutti gli ogetti è

List<F1Team> teams = F1Data.GetTeams();
var query = from team in teams select team;

Questo esempio di query eseguito su un oggetto IEnumerable non tipizzato

ArrayList list = new ArrayList();
list.AddRange(teams);
var query = from F1Team team in list select team.TeamName;

Una query che estrae solo il nome di tutti i teams è

var query = teams.Select(team => team.TeamName);

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

var query = from team in teams
from pilots in team.Pilots
where pilot.LastName.Lenght > 8
where team.Pilots.Lenght > 1
select pilot.LastName;

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

var query = from team in teams
let wins = team.Wins
ordered wins ascending
where wins > 3
select team.TeamName + “:” + wins;

Supponiamo di voler estrarre tutti i tems, ordine decrescente per punti, che hanno un pilota con più di 50 punti

var query = from team in teams
let wins = team.Wins
let pilots = team.Pilots
from pilot in pilots
where pilot.Point > 50
orderby wins descending
select team.TeamName + ” wins:” + wins + “, leader ” + pilot.LastName + ” ” + pilot.Point;

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

var query = from team in teams
select new Tuple<string, int>(team.TeamName, team.Wins);
foreach (Tuple<string, int> t in query) Console.WriteLine($”{t.Item1}: {t.Item2}”);

Una seconda opzione è istanziare un oggetto anonimo impostando campi e proprietà pubbliche

var query = from team in teams
select new { TeamName = team.TeamName, Wins = team.Wins };
foreach (var t in query) Console.WriteLine($”{t.TeamName}: {t.Wins}”);

Possiamo realizzare la stessa cosa utilizzando l’espressioni lambda

var query = teams.Select(team => new { team.TeamName, team.Wins });

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

var query1 = from team in teams
let pilots = team.Pilots
from pilot in pilots
where pilot.LastName.Length > 7
select new { TeamName = team.TeamName, PilotName = pilot.LastName };


oppure

var query = teams.SelectMany(team => team.Pilots.Where(pilot => pilot.LastName.Length > 7), (team, pilot) => team.TeamName + ” – ” + pilot.LastName);

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

var query = from team in teams
from pilot in team.Pilots
group pilot by 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

foreach (IGrouping<F1Team, Pilot> group in query)
{
F1Team team = group.Key;
Console.WriteLine ($”Team: {team}”);
foreach (Pilot pilot in group)
{ Console.WriteLine ($”- {pilot.FirstName} {pilot.LastName}”); }
}

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

var query = from team in teams
where team.Wins > 2
select team.Pilots
into topTeamPilots
from tp in topTeamPilots
where tp.Point > 50
orderby tp.Point descending
select tp.LastName + ” ” + tp.FirstName + “: ” + tp.Point;

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

var query = from team in teams
from pilot in team.Pilots
join country in Country.All on pilot.IDCountry equals country.IDCountry
select new { pilot.LastName, CountryName = country.Name };

oppure

var query = teams.SelectMany(t => t.Pilots).Join(Country.All, pilot => pilot.IDCountry, country => country.IDCountry,
(p, c) => new {p.LastName, CountryName = c.Name});

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.

var pilots = from team in teams orderby team.TeamName
from pilot in team.Pilots
select pilot;

var query = from country in Country.All
join pilot in pilots on country.IDCountry equals pilot.IDCountry
into pilotsxCountry
select new { CountryName = country.Name, Pilots = pilotsxCountry };

oppure

IEnumerable pilots = teams.SelectMany(t => t.Pilots);
var query = Country.All.GroupJoin(pilots, country => country.IDCountry, pilot => pilot.IDCountry, (c, p) => new {CountryName = c.Name, Pilots = p});
foreach (var group in query)
{
Console.WriteLine(group.CountryName);
foreach (Pilot pilot in group.Pilots)
{
Console.WriteLine($” – {pilot.LastName}”);
}
}

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;
var qDist = array1.Distinct();
var qUni = array1.Union(array2);
var qExc = array1.Except(array2);
var qInt = array1.Intersect(array2);
var qZip = array1.Zip(array2, (a,b) => a*10+b);

aaaaa
fine