8. diel - LINQ provideri, anonymné typy, radenie a zoskupovanie
V minulej lekcii, LINQ v C# .NET - Revolúcia v dopytovaní, sme si uviedli technológiu LINQ a vytvorili svoj prvý jednoduchý dotaz.
Dnes budeme v C# .NET tutoriále s technológiou LINQ pokračovať.
Provideri
Spomenuli sme si, že LINQ funguje vďaka tzv. providerom. To sú implementácie metód pre rôzne platformy, aby na nich bolo možné volať LINQ dotazy. Vymenujme si základné providery pre LINQ a ku každému si niečo málo povedzme.
Provideri v .NET
Niektoré providery dodáva Microsoft priamo v .NET frameworku, nás budú zaujímať najmä nasledujúce:
- LINQ to Objects - Provider umožňuje klásť dotazy nad
základnými kolekciami. Používali sme ho minule pre dopytovanie nad poľom,
rovnako tak sa môžeme pýtať nad dátami v kolekcii
List
a ďalšími kolekciami z .NET. LINQ to Objects teda pracuje s dátami v operačnej pamäti. - LINQ to SQL/LINQ to Entities - Provider mapuje LINQ príkazy na SQL dotazy a umožňuje nám pracovať s MS-SQL databázou. Provider nám poskytuje tzv. ORM (Object-relation mapping, viď ďalšie diely kurzu). Práca s databázou je plne objektová, v kurzoch budeme často pracovať s providerom LINQ to Entities, LINQ to SQL je jeho predchodca. Linq to Entities je súčasťou tzv. Entity frameworku, čo je pomerne rozsiahla knižnica na prácu s relačnými (databázovými) dátami.
- LINQ to XML - Provider umožňuje dotazy nad XML súborom. Rovnako ako pri LINQ to SQL ide o objektový prístup.
Provideri tretích strán
Keďže je samozrejme možné napísať si provider vlastný, mnoho hotových riešení existuje aj od tretích strán. Avšak, niektorí z nich nie sú príliš dobre podporovaní a je veľmi dobrý nápad držať sa skôr Microsoft technológií:
- DbLinq - Najpoužívanejší provider tretej strany, ktorý umožňuje používať technológiu LINQ na databázach MySQL, SQLite, Oracle, Postgre SQL, Firebird a ďalších veľmi používaných databázových platformách.
- LINQ to Excel - Provider umožňuje pracovať s Excelovou tabuľkou (Excelovým dokumentom) ako s databázou.
- LINQ to JSON - Provider pre dotazovanie nad súbory formátu JSON.
- LINQ to Amazon - LINQ to Amazon sa často uvádza ako ukážka providera tretej strany. Pomocou LINQ dotazov môžeme vyhľadávať v knihách, ktoré tento internetový obchod ponúka.
Vidíme, že providerov je dostatok a akonáhle vieme LINQ používať, nie je problém ho použiť takmer na všetko. V ďalších kurzoch sa budeme venovať LINQ to XML a LINQ to Entities.
Anonymné typy
Aby sme mohli vytvárať zložitejšie dotazy, presnejšie dostať z dotazu len tú časť dát, ktorá nás zaujíma, budeme používať tzv. anonymné typy. Anonymný typ sa chová ako inštancia triedy, ale žiadnu triedu na tento účel deklarovať nemusíme. Skúsme si to:
{CSHARP_CONSOLE}
var anonym = new { FirstName="Anonymous", LastName="Very anonymous", Age="18" };
Console.WriteLine(anonym.FirstName);
Console.WriteLine(anonym.LastName);
Console.WriteLine(anonym.Age);
{/CSHARP_CONSOLE}
Výstup takého programu bude nasledujúci:
Konzolová aplikácia
Anonymous
Very anonymous
18
Vytvorili sme si anonymný dátový typ, ktorý má v sebe tri atribúty. Do
nich sme rovno uložili hodnoty a celú štruktúru vložili do premennej typu
var
. Výsledný dátový typ je veľmi podivný a bez kľúčového
slova var
by sme takú premennú ťažko vytvorili.
Na čo je výhoda takýchto typov? V dotazoch si môžeme v
select
namapovať úplne, čo chceme. Majme kolekciu s autami a
vodičmi. Budeme chcieť vybrať vždy ŠPZ auta a celé meno jeho vodiča pri
červených autách. Vodič má na sebe vlastnosti FirstName
a
LastName
, auto má na sebe vlastnosti Color
,
Driver
a LicencePlate
. Dotaz by vyzeral takto:
var query = from c in cars where (c.Color == "red") select new { c.LicencePlate, DriverName = c.Driver.FirstName + " " + c.Driver.LastName };
Aby nám dotaz mohol vrátiť takto zložitý výsledok, musíme si naň
vytvoriť práve anonymný dátový typ. Výsledok dotazu bude teda kolekcia
prvkov, kde každý prvok bude mať vlastnosti LicencePlate
a
DriverName
. Všimnite si, že sme raz napísali len
c.LicencePlate
a druhýkrát DriverName = ...
Ak
chceme vložiť už nejakú existujúcu vlastnosť s rovnakým názvom, stačí
uviesť iba tú. Ak chceme názov iný, musíme použiť
PropertyName = value
.
Pozor: Anonymnými typmi by sme mali šetriť rovnako ako
napr. s kľúčovým slovom var
. Opäť sa jedná o funkcionalitu
vytvorenú na určité účely, hlavne pre LINQ dotazy. Nepoužívajte
anonymné typy v bežnom programovaní namiesto tried, znižujete tak kvalitu
zdrojového kódu aj výsledné aplikácie. Atribúty anonymných typov
sú len na čítanie, presnejšie sa jedná o
vlastnosti. Väčšinou sa chceme anonymným typom vyhnúť a
radšej navrhneme dátovú vrstvu aplikácie tak, aby sme nemuseli písať
príliš zložité dotazy. Použitím anonymných typov sa zbavujeme relácií
medzi jednotlivými entitami, čo môže viesť v zložitejšej aplikácii k
mnohým problémom. Lepšie by sme konkrétnu situáciu mohli riešiť tak, že
autu vytvoríme vlastnosť DriverName
a v select
vyberieme celé auto, teda jednoducho select c
. Meno vodiča si
potom vytiahneme rovnako jednoducho, ako s anonymným typom. Zároveň pracujeme
s celým autom vrátane všetkých väzieb a nie s nejakou
neidentifikovateľnou anonymnou entitou. Rovnako ale majú anonymné typy
svoje miesto pre zložitejšie, najmä jednoúčelové dotazy a budú sa často
objavovať v ukážkových dopytoch v týchto tutoriáloch.
Už vieme všetko potrebné na to, aby sme si ukázali syntax pre pokročilejšie dotazy.
Radenie a zoskupovanie
Minule sme si uviedli kľúčové slová from
,
where
a select
. Ukážme si ešte dvoch základných
operátorov, než sa pustíme do podrobného popisu syntaxe LINQ.
orderby
Pokiaľ chceme výsledky dotazu zoradiť, použijeme operátor
orderby
:
{CSHARP_CONSOLE} User sophie = new User("Sophie", "O'Neill", 25); User james = new User("James", "Vines", 48); User omar = new User("Omar", "Smith", 12); User timothy = new User("Timothy", "Engelman", 24); User bill = new User("Bill", "Gibbs", 73); User sylvia = new User("Sylvia", "Bracero", 29); List<User> users = new List<User>{ sophie, james, omar, timothy, bill, sylvia}; var query = from u in users where (u.Age > 15) orderby u.FirstName select u.Age; foreach (int i in query) Console.WriteLine(i); {/CSHARP_CONSOLE}
{CSHARP_OOP} class User { public string FirstName { get; private set; } public string LastName { get; private set; } public int Age { get; private set; } public User(string firstName, string lastName, int age) { FirstName = firstName; LastName = lastName; Age = age; } public override string ToString() { return FirstName + " " + LastName; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikácia
73
48
25
29
24
Ako už vieme, C# si tento dotaz vnútorne preloží na metódy:
var query = users.Where(u => u.Age > 15).OrderBy(u => u.FirstName).Select(u => u.Age);
Všetky klauzuly z LINQ majú svoje metódy, to bolo len pre upomenutie, ďalej už nebudem "metódovú" verziu pri ukážkach uvádzať.
Predvolený smer radenia je od najmenších hodnôt po najväčší (tu
konkrétne radíme podľa mena užívateľa, teda od A do Z). Ak chceme radiť
opačne, uvedieme kľúčové slovo descending
:
{CSHARP_CONSOLE} User sophie = new User("Sophie", "O'Neill", 25); User james = new User("James", "Vines", 48); User omar = new User("Omar", "Smith", 12); User timothy = new User("Timothy", "Engelman", 24); User bill = new User("Bill", "Gibbs", 73); User sylvia = new User("Sylvia", "Bracero", 29); List<User> users = new List<User>{ sophie, james, omar, timothy, bill, sylvia}; var query = from u in users where (u.Age > 15) orderby u.FirstName descending select u.Age; foreach (int i in query) Console.WriteLine(i); {/CSHARP_CONSOLE}
{CSHARP_OOP} class User { public string FirstName { get; private set; } public string LastName { get; private set; } public int Age { get; private set; } public User(string firstName, string lastName, int age) { FirstName = firstName; LastName = lastName; Age = age; } public override string ToString() { return FirstName + " " + LastName; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikácia
24
29
25
48
73
V pôvodnom príklade sme mohli použiť ascending
, ale nie je
to nutné. Pokiaľ ide o metódy, použije sa
OrderByDescending()
.
Radiť môžeme aj podľa viacerých kritérií, jednoducho ich oddelíme čiarkou, tie prvé majú prednosť:
{CSHARP_CONSOLE} User sophie = new User("Sophie", "O'Neill", 25); User james = new User("James", "Vines", 48); User omar = new User("Omar", "Smith", 12); User timothy = new User("Timothy", "Engelman", 24); User bill = new User("Bill", "Gibbs", 73); User sylvia = new User("Sylvia", "Bracero", 29); List<User> users = new List<User>{ sophie, james, omar, timothy, bill, sylvia}; var query = from u in users where (u.Age > 15) orderby u.FirstName, u.LastName select u.Age; foreach (int i in query) Console.WriteLine(i); {/CSHARP_CONSOLE}
{CSHARP_OOP} class User { public string FirstName { get; private set; } public string LastName { get; private set; } public int Age { get; private set; } public User(string firstName, string lastName, int age) { FirstName = firstName; LastName = lastName; Age = age; } public override string ToString() { return FirstName + " " + LastName; } } {/CSHARP_OOP}
Výsledok:
Konzolová aplikácia
73
48
25
29
24
Tento dotaz si C# preloží na:
var query = users.Where(u => u.Age > 15).OrderBy(u => u.FirstName).ThenBy(u => u.LastName).Select(u => u.Age);
group
- by
(zoskupovanie)
V dotazoch často využívame zoskupovanie (grouping). Môžeme tak jednoducho prvky zoskupiť podľa určitých kritérií. Ukážme si príkad, ako by sme zoskupili užívateľov do skupín podľa ich veku:
var query = from u in users group u by u.Age into ageGroup select new { Age = ageGroup.Key, Users = ageGroup };
Čo že sme to urobili? Zoskupili sme používateľov podľa ich veku do
ageGroup
, čo je kolekcia, ktorá obsahuje vždy používateľov s
rovnakým vekom. Je tam teda napr. skupina 24
, skupina
25
, 29
atď. Ďalej vyberáme, ako bude skupina
vyzerať. Bude obsahovať vek, ten vezmeme z kľúča skupiny, ktorým je práve
vek, pretože podľa neho zoskupujeme. Druhou vlastnosťou skupiny bude kolekcia
používateľov, tam uložíme tú aktuálnu skupinu používateľov pre daný
vek.
Prejdime k výpisu:
{CSHARP_CONSOLE} User sophie = new User("Sophie", "O'Neill", 25); User james = new User("James", "Vines", 48); User omar = new User("Omar", "Smith", 12); User timothy = new User("Timothy", "Engelman", 24); User bill = new User("Bill", "Gibbs", 73); User sylvia = new User("Sylvia", "Bracero", 29); List<User> users = new List<User>{ sophie, james, omar, timothy, bill, sylvia}; var query = from u in users group u by u.Age into ageGroup select new { Age = ageGroup.Key, Users = ageGroup }; foreach (var group in query) { Console.WriteLine(group.Age); foreach (var user in group.Users) Console.WriteLine(user.FirstName); } {/CSHARP_CONSOLE}
{CSHARP_OOP} class User { public string FirstName { get; private set; } public string LastName { get; private set; } public int Age { get; private set; } public User(string firstName, string lastName, int age) { FirstName = firstName; LastName = lastName; Age = age; } } {/CSHARP_OOP}
Dotaz vyberie:
Konzolová aplikácia
25
Sophie
48
James
12
Omar
24
Timothy
73
Bill
29
Sylvia
Najprv proiterujeme všetky skupiny a pre každú skupinu vypíšeme jej vek a potom používateľa v nej obsiahnutej.
V nasledujúcom kvíze, Kvíz - Slovníky, množiny, front, zásobník v C# .NET Kolekcia, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.