LINQ je, slobodno možemo reći, uveliko promijenio način programiranja u .NET programskim jezicima na način da je nešto što je prije LINQ-a izgledalo jako komplikovano i vrlo zahtjevno danas sa LINQom jednostavno, lagano i „vrlo prosto“.
O LINQ-u sam najviše napisao blog postova. Skoro da nema posta ovdje koji se nekim dijelom ne dotiče LINQa. Ali, s LINQom uvijek se nešto otkrije i pokuša primjeniti u stvarnom primjeru, a inspiraciju nađemo ili u standradnom SQL jeziku ili je inspiracija pronađena u nekom od postojećih algoritama.
Danas ćemo vidjeti kako se preko LINQa implementira LEFT i RIGHT JOIN, koji predstavljaju osnovne operatore SQL jezika. Šta je JOIN odnosno LEFT i RIGHT JOIN možete pogledati na ovom linku , a jednostavno možemo reći da su to osnovni operatoru spajanja dva ili više skupova podataka.
Obični JOIN vrlo lako možemo implementirati preko LINQ. Npr. pretpostavimo da imamo dva tipa podataka: Vehicle tip i VehicleType tip podataka. Pretpostavimo i to da svaki Vehicle tip sadrži osobinu koja nam govori o kakvom VehicleType tipu je riječ. Prevedeno na domaći jezik možemo kazati da svako Vozilo (eng. Vehicle) pripada nekom tipu vozila (eng. VehicleType). Neka imamo sljedeću deklaraciju tipova podataka:
class VehicleGroup { public int? ID; public string Name; } class VehicleType { public int? ID; public string Name; public int? vehiclegroupID; } class Vehicle { public int? ID; public string Name; public int? vehicletypeID; }
Svako vozilo (Vehicle) pripada tipu vozila, a tipovi vozila pripadaju određenoj grupi vozila (VehicleGroup). Kada bi željeli da izlistamo sva vozila sa pripadajućem tipom i grupom, LINQ izraz bi izgedao kao na sljedeći način:
//List all vehicles with coresponded types and groups without LEFT JOIN var q = from v in vehicles from t in types where v.vehicletypeID == t.ID from g in groups where t.vehiclegroupID == g.ID select new { VehicleID = v.ID, VehicleName = v.Name, VehicleTypeName = t.Name, VehicleGroupName = g.Name };
Ovdje se može primjetiti da je operator JOIN u LINQ implementiran preko WHERE operatora. Mana ovog izraza jesta ta da u koliko ne postoji Vehicle objekat nekog specifičnog VehicleType tipa odnosno grupe, dotični tip i i grupa se neće pojaviti u rezultatu.
U koliko želimo da izlistamo sve grupe i tipove bez obzira da li imamo Vehicle tip definisan, to nećemo moći gornjom implementacijom. Ista situacija se dešava i u relacijskim bazama podataka pa je nužno koristiti LEFT ili RIGHT JOIN.
Da bi prikazali sve grupe i tipove bez obzira da li za dotične postoji Vehicle tip implementacija LINQa data je na sljedećem listingu:
//List all vehicles with coresponded types and groups with RIGHT JOIN, //If we want LEFT JOIN from below query order of the collections will be as in the previous query var q = from grp in groups from typ in types .Where(t=>t.vehiclegroupID==grp.ID) .DefaultIfEmpty()//this is important method for RIGHT JOIN from veh in vehicles .Where(v=>v.vehicletypeID == typ.ID) .DefaultIfEmpty()//this is important method for RIGHT JOIN select new { VehicleID = veh==null?0: veh.ID, VehicleName = veh == null ? "unknown" : veh.Name, VehicleTypeName = typ==null? "unknown" : typ.Name, VehicleGroupName = grp==null? "unknown":grp.Name };
Vidimo da je implementacija RIGHT JOIN slična prethodnoj implementaciji. Razlika se sastoji u poretku kolekcija, gdje ovdje idemo od najvišeg tipa po hijearhiji do najnižeg. Druga stvar koju smo ovdje koristili jeste metoda DefaultIfEmpty() koja nam sprečava da LINQ upit “pukne” kada se u procesuiranju dođe do null vrijednosti. Na kraju pravimo novi tip u koji unosimo vrijednosti iz svih kolekcija. Međutim pošto se ovdje može desiti da Vehicle ne postoji za određeni tip ili da tip i vehicle ne postoji za određenu grupu, prilikom inicijalizacije kolekcije sa rezultatom provjeravamo da li je dotični property null. Na ovaj način se također sprečavamo da nam LINQ izraz ne “pukne” u toku procesuiranja. LEFT JOIN se jednostavno implementira na način da ovom upitu zamjenimo poredak kolekcija, odnosno da poredak bude isti kao u prvom upitu, a ostalo ostaje isto.
Na kraju napravimo jednostavni demo koji će nam demonstrirati prethodno rečeno. Implementirajno prezentirane kolekcije vehicle, types i group sa podacima, ali na taj način da imamo jedan VehicleType koji neće sadržavati ni jedan Vehicle.
private static void InitData(out List groups,out List types,out List vehicles) { groups = new List() { new VehicleGroup() { ID=0, Name="Unknown" }, new VehicleGroup() { ID=1, Name= "Group1" }, new VehicleGroup() { ID=2, Name= "Group2" } }; types = new List() { new VehicleType() { ID=0, Name="Unknown", vehiclegroupID=0 }, new VehicleType() { ID=1, Name= "Type1", vehiclegroupID=1 }, new VehicleType() { ID=2, Name= "Type2", vehiclegroupID=1 }, new VehicleType() { ID=3, Name= "Type3", vehiclegroupID=2 }, new VehicleType() { ID=4, Name= "Type4", vehiclegroupID=1 } }; vehicles = new List() { new Vehicle() { ID=0, Name="Unknown", vehicletypeID=0 }, new Vehicle() { ID=1, Name= "Vehicle1", vehicletypeID=1 }, new Vehicle() { ID=2, Name= "Vehicle2", vehicletypeID=2 }, new Vehicle() { ID=3, Name= "Vehicle3", vehicletypeID=3 }, new Vehicle() { ID=4, Name= "Vehicle4", vehicletypeID=1 }, new Vehicle() { ID=5, Name= "Vehicle5", vehicletypeID=2 }, new Vehicle() { ID=6, Name= "Vehicle6", vehicletypeID=2 }, new Vehicle() { ID=7, Name= "Vehicle7", vehicletypeID=2 }, new Vehicle() { ID=8, Name= "Vehicle8", vehicletypeID=2 }, new Vehicle() { ID=9, Name= "Vehicle9", vehicletypeID=3 }, new Vehicle() { ID=10, Name= "Vehicle10", vehicletypeID=3 } }; }
Sada implementirajmo obični i RIGHT JOIN a rezultat pokretanja demo aplikacije vidimo na sljedećoj slici:
Sa slike se može vidjeti da u prvom primjeru imamo 11 stavki, a u drugoj 12. 12-ta stavka je upravo ona stavka koja nije prikazana u prvom slučaju, odnosno nije prikazana stavka sa VehicleType.Name=”Type4″.
Cjelokupan demo može se preuzeti sa ovog linka.