Lekce 8 - LINQ provideři, 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 svůj první jednoduchý dotaz.
Dnes budeme v C# .NET tutoriálu s LINQem pokračovat.
Provideři
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.
Provideři 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
List
y a dalšími kolekcemi z .NETu. 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-relation mapping, viz další díly kurzu). Práce s databází je plně objektová, v kurzech 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.
Provideři 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. Nicméně, někteří z nich nejsou příliš dobře podporováni a je 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, Postgre SQL, Firebird a 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 použít 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ě 3 atributy. Do
nich jsme rovnou uložili hodnoty a celou strukturu 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 těžko vytvořili.
K čemu je výhoda takových typů? V dotazech si můžeme v
select
u 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ýsledek dotazu bude tedy kolekce
prvků, kde každý prvek bude mít vlastnosti Spz
a
JmenoRidice
. Všimněte si, že jsme jednou napsali jen
a.Spz
a podruhé JmenoRidice = ....
Pokud chceme
vložit již nějakou 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ř. s klíčovým slovem var
. Opět se jedná o funkcionalitu
vytvořenou pro určité účely, zejména do LINQ dotazů.
Nepoužívejte anonymní typy 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. Většinou se chceme anonymním typům
vyhnout a raději navrhneme datavou vrstvu aplikace tak, abychom nemuseli jsme
psát příliš složité dotazy. Použitím anonymních typů se zbavujeme
relací mezi jednotlivými entitami, což může vést ve složitější
aplikaci k mnoha problémům. Lépe bychom konkrétní situaci mohli řešit
tak, že autu vytvoříme vlastnost JmenoRidice
a v
select
u vybereme celé auto, tedy jednoduše select a
.
Jméno řidiče si poté vytahneme 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 pro složitější, zejména jednoúčelové dotazy a budou se
často objevovat v ukázkových dotazech v těchto tutoriálech.
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
. Ukažme si ještě 2 základní operátory, než se
pustíme do podrobného popisu syntaxe LINQ.
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, to bylo jen pro upomenutí, dále již nebudu "metodovou" verzi u ukázek uvádět.
Výchozí směr řazení je od nejmenších hodnot po 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é. Pokud jde o metody, použije se OrderByDescending()
.
Řadit můžeme i podle více kritérií, jednoduše je oddělíme čárkou, ta první 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érii. Ukažme si příkad 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 };
Co že jsme to provedli? 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ů, tam uložíme tu 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 skupiny 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í.