Lekce 8 - LINQ providery, anonymní typy, řazení a seskupování
V minulé lekci, LINQ v C# .NET - Revoluce v dotazování, jsme si uvedli technologii LINQ a vytvořili jsme si svůj první jednoduchý dotaz.
Dnes budeme v C# .NET tutoriálu s technologií LINQ pokračovat.
Providery
Zmínili jsme si, že LINQ funguje díky tzv. providerům. To jsou implementace metod pro různé platformy, aby na nich bylo možné volat LINQ dotazy. Vyjmenujme si základní providery pro LINQ a ke každému si něco málo řekněme.
Providery v .NET
Některé providery dodává Microsoft přímo v .NET frameworku. Nás budou zajímat zejména následující:
- LINQ to Objects – Provider umožňuje klást dotazy nad
základními kolekcemi. Používali jsme ho minule pro dotazy nad polem, stejně
tak se můžeme dotazovat nad dalšími kolekcemi z .NET, jako je
List
. LINQ to Objects tedy pracuje s daty v operační paměti. - LINQ to SQL / LINQ to Entities – Provider mapuje LINQ příkazy na SQL dotazy a umožňuje nám pracovat s MS-SQL databází. Provider nám poskytuje tzv. ORM (Object-relational mapping, viz další díly kurzu). Práce s databází je plně objektová, v kurzech proto budeme často pracovat s providerem LINQ to Entities. LINQ to SQL je jeho předchůdce. LINQ to Entities je součástí tzv. Entity frameworku, což je poměrně rozsáhlá knihovna pro práci s relačními (databázovými) daty.
- LINQ to XML – Provider umožňuje dotazy nad XML souborem. Stejně jako u LINQ to SQL se jedná o objektový přístup.
Providery třetích stran
Jelikož je samozřejmě možné napsat si provider vlastní, mnoho hotových řešení existuje i od třetích stran. Některé z nich však nejsou příliš dobře podporovány, a je proto velmi dobrý nápad držet se spíše Microsoft technologií.
- DbLinq – Nejpoužívanější provider třetí strany, který umožňuje používat technologii LINQ na databázích MySQL, SQLite, Oracle, PostgreSQL, Firebird a na dalších velmi používaných databázových platformách.
- LINQ to Excel – Provider umožňuje pracovat s excelovou tabulkou (excelovým dokumentem) jako s databází.
- LINQ to JSON – Provider pro dotazování nad soubory formátu JSON.
- LINQ to Amazon – LINQ to Amazon se často uvádí jako ukázka provideru třetí strany. Pomocí LINQ dotazů můžeme vyhledávat v knihách, které tento internetový obchod nabízí.
Vidíme, že providerů je dostatek, a jakmile umíme LINQ používat, není problém ho aplikovat téměř na vše. V dalších kurzech se budeme věnovat LINQ to XML a LINQ to Entities.
Anonymní typy
Abychom mohli vytvářet složitější dotazy, přesněji dostat z dotazu jen tu část dat, která nás zajímá, budeme používat tzv. anonymní typy. Anonymní typ se chová jako instance třídy, ale žádnou třídu k tomuto účelu deklarovat nemusíme. Zkusme si to:
{CSHARP_CONSOLE}
var anonym = new { Jmeno="Anonym", Prijmeni="Anonymní", Vek="18" };
Console.WriteLine(anonym.Jmeno);
Console.WriteLine(anonym.Prijmeni);
Console.WriteLine(anonym.Vek);
{/CSHARP_CONSOLE}
Výstup takového programu bude následující:
Konzolová aplikace
Anonym
Anonymní
18
Vytvořili jsme si anonymní datový typ, který má v sobě tři atributy.
Do nich jsme rovnou uložili hodnoty a celou strukturu jsme vložili do
proměnné typu var
. Výsledný datový typ je velmi podivný a bez
klíčového slova var
bychom takovou proměnnou vytvořili
těžko.
V čem spočívá výhoda takových typů? V dotazech si můžeme v
select
namapovat úplně, co chceme. Mějme kolekci s auty a
řidiči. Budeme chtít vybrat vždy SPZ auta a celé jméno jeho řidiče u
červených aut. Řidič má na sobě vlastnosti Jmeno
a
Prijmeni
, auto má na sobě vlastnosti Barva
,
Ridic
a Spz
. Dotaz by vypadal takto:
var dotaz = from a in auta where (a.Barva == "cervena") select new { a.Spz, JmenoRidice = a.Ridic.Jmeno + " " + a.Ridic.Prijmeni };
Aby nám dotaz mohl vrátit takto složitý výsledek, musíme si na něj
vytvořit právě anonymní datový typ. Výsledkem dotazu tedy bude kolekce
prvků, kde každý prvek bude mít vlastnosti Spz
a
JmenoRidice
. Všimněme si, že jsme jednou napsali jen
a.Spz
a podruhé JmenoRidice = ...
. Pokud chceme
vložit nějakou již existující vlastnost se stejným názvem, stačí uvést
pouze tu. Pokud chceme název jiný, musíme použít
NazevVlastnosti = hodnota
.
Pozor: Anonymními typy bychom měli šetřit stejně jako
např. klíčovým slovem var
. Opět se jedná o funkcionalitu
vytvořenou pro určité účely, zejména do LINQ dotazů. Anonymní
typy nepoužívejte v běžném programování namísto tříd, snižujete tak
kvalitu zdrojového kódu i výsledné aplikace. Atributy anonymních
typů jsou jen pro čtení, přesněji se jedná o
vlastnosti. Anonymním typům se většinou chceme vyhnout a
raději navrhneme datovou vrstvu aplikace tak, abychom nemuseli psát příliš
složité dotazy. Použitím anonymních typů se zbavujeme relací mezi
jednotlivými entitami, což může ve složitější aplikaci vést k mnoha
problémům. Lépe bychom konkrétní situaci mohli vyřešit tak, že autu
vytvoříme vlastnost JmenoRidice
a v select
vybereme
celé auto, tedy jednoduše select a
. Jméno řidiče si poté
vytáhneme stejně jednoduše jako s anonymním typem. Zároveň pracujeme s
celým autem včetně všech vazeb, a ne s nějakou neidentifikovatelnou
anonymní entitou. Stejně tak ale mají anonymní typy své místo ve
složitějších, zejména jednoúčelových dotazech, a budou se proto v
ukázkových dotazech v těchto tutoriálech často objevovat.
Již umíme vše potřebné k tomu, abychom si ukázali syntaxi pokročilejších dotazů.
Řazení a seskupování
Minule jsme si uvedli klíčová slova from
, where
a select
. Než se pustíme do podrobného popisu syntaxe LINQ,
ukažme si ještě dva základní operátory.
orderby
Pokud chceme výsledky dotazu seřadit, použijeme operátor
orderby
:
{CSHARP_CONSOLE} Osoba karel = new Osoba("Karel", "Novák", 15); Osoba josef = new Osoba("Josef", "Nový", 24); Osoba jan = new Osoba("Jan", "Marek", 13); Osoba marie = new Osoba("Marie", "Nová", 18); Osoba michaela = new Osoba("Simona", "Mladá", 24); Osoba simona = new Osoba("Michaela", "Marná", 40); List<Osoba> uzivatele = new List<Osoba>{ jan, josef, karel, marie, michaela, simona}; var dotaz = from u in uzivatele where (u.Vek > 15) orderby u.Jmeno select u.Vek; foreach (int i in dotaz) Console.WriteLine(i); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Osoba { public string Jmeno { get; private set; } public string Prijmeni{ get; private set; } public int Vek { get; private set; } public Osoba(string jmeno, string prijmeni, int vek) { Jmeno = jmeno; Prijmeni = prijmeni; Vek = vek; } public override string ToString() { return Jmeno + " " + Prijmeni; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikace
24
18
40
24
Jak již víme, C# si tento dotaz vnitřně přeloží na metody:
var dotaz = uzivatele.Where(u => u.Vek > 15).OrderBy(u => u.Jmeno).Select(u => u.Vek);
Všechny klauzule z LINQ mají své metody a uvedli jsme je pouze pro upomenutí. Dále již „metodovou“ verzi u ukázek uvádět nebudeme.
Výchozí směr řazení je od nejmenších hodnot po ty největší (zde
konkrétně řadíme podle jména uživatele, tedy od A do Z). Pokud chceme
řadit opačně, uvedeme klíčové slovo descending
:
{CSHARP_CONSOLE} Osoba karel = new Osoba("Karel", "Novák", 15); Osoba josef = new Osoba("Josef", "Nový", 24); Osoba jan = new Osoba("Jan", "Marek", 13); Osoba marie = new Osoba("Marie", "Nová", 18); Osoba michaela = new Osoba("Simona", "Mladá", 24); Osoba simona = new Osoba("Michaela", "Marná", 40); List<Osoba> uzivatele = new List<Osoba>{ jan, josef, karel, marie, michaela, simona}; var dotaz = from u in uzivatele where (u.Vek > 15) orderby u.Jmeno descending select u.Vek; foreach (int i in dotaz) Console.WriteLine(i); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Osoba { public string Jmeno { get; private set; } public string Prijmeni{ get; private set; } public int Vek { get; private set; } public Osoba(string jmeno, string prijmeni, int vek) { Jmeno = jmeno; Prijmeni = prijmeni; Vek = vek; } public override string ToString() { return Jmeno + " " + Prijmeni; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikace
24
40
18
24
V původním příkladu jsme mohli použít ascending
, ale není
to nutné. Co se metod týče, použije se OrderByDescending()
.
Řadit můžeme i podle více kritérií, jednoduše je oddělíme čárkou. První kritéria mají přednost:
{CSHARP_CONSOLE} Osoba karel = new Osoba("Karel", "Novák", 15); Osoba josef = new Osoba("Josef", "Nový", 24); Osoba jan = new Osoba("Jan", "Marek", 13); Osoba marie = new Osoba("Marie", "Nová", 18); Osoba michaela = new Osoba("Simona", "Mladá", 24); Osoba simona = new Osoba("Michaela", "Marná", 40); List<Osoba> uzivatele = new List<Osoba>{ jan, josef, karel, marie, michaela, simona}; var dotaz = from u in uzivatele where (u.Vek > 15) orderby u.Jmeno, u.Prijmeni select u.Vek; foreach (int i in dotaz) Console.WriteLine(i); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Osoba { public string Jmeno { get; private set; } public string Prijmeni{ get; private set; } public int Vek { get; private set; } public Osoba(string jmeno, string prijmeni, int vek) { Jmeno = jmeno; Prijmeni = prijmeni; Vek = vek; } public override string ToString() { return Jmeno + " " + Prijmeni; } } {/CSHARP_OOP}
Výsledek:
Konzolová aplikace
24
18
40
24
Tento dotaz si C# přeloží na:
var dotaz = uzivatele.Where(u => u.Vek > 15).OrderBy(u => u.Jmeno).ThenBy(u => u.Prijmeni).Select(u => u.Vek);
group
-by
(seskupování)
V dotazech často využíváme seskupování (grouping). Můžeme tak jednoduše prvky seskupit podle určitých kritérií. Ukažme si příklad, jak bychom seskupili uživatele do skupin podle jejich věku:
var dotaz = from u in uzivatele group u by u.Vek into vekovaSkupina select new { Vek = vekovaSkupina.Key, Uzivatele = vekovaSkupina };
Postup je následující. Seskupili jsme uživatele podle jejich věku do
vekovaSkupina
, což je kolekce, která obsahuje vždy uživatele se
stejným věkem. Je tam tedy např. skupina 15
, skupina
16
, 17
atd. Dále vybíráme, jak bude skupina
vypadat. Bude obsahovat věk. Ten vezmeme z klíče skupiny, kterým je právě
věk, jelikož podle něj seskupujeme. Druhou vlastností skupiny bude kolekce
uživatelů, kam uložíme aktuální skupinu uživatelů pro daný věk.
Přejděme k výpisu:
{CSHARP_CONSOLE} Osoba karel = new Osoba("Karel", "Novák", 15); Osoba josef = new Osoba("Josef", "Nový", 24); Osoba jan = new Osoba("Jan", "Marek", 13); Osoba marie = new Osoba("Marie", "Nová", 18); Osoba simona = new Osoba("Simona", "Mladá", 24); Osoba michaela = new Osoba("Michaela", "Marná", 14); List<Osoba> uzivatele = new List<Osoba>{ karel, josef, jan, marie, simona, michaela }; var dotaz = from u in uzivatele group u by u.Vek into vekovaSkupina select new { Vek = vekovaSkupina.Key, Uzivatele = vekovaSkupina }; foreach (var skupina in dotaz) { Console.WriteLine(skupina.Vek); foreach (var uzivatel in skupina.Uzivatele) Console.WriteLine(uzivatel.Jmeno); } {/CSHARP_CONSOLE}
{CSHARP_OOP} class Osoba { public string Jmeno { get; private set; } public string Prijmeni{ get; private set; } public int Vek { get; private set; } public Osoba(string jmeno, string prijmeni, int vek) { Jmeno = jmeno; Prijmeni= prijmeni; Vek = vek; } } {/CSHARP_OOP}
Dotaz vybere:
Konzolová aplikace
15
Karel
24
Josef
Simona
13
Jan
18
Marie
14
Michaela
Nejprve proiterujeme všechny skupiny a pro každou skupinu vypíšeme její věk a poté uživatele v ní obsažené.
V následujícím kvízu, Kvíz - Slovníky, množiny, fronta, zásobník v C# .NET Kolekce, si vyzkoušíme nabyté zkušenosti z předchozích lekcí.