Table of Contents
LINQ
Odkazy
Příklad na LINQtoSQL
LINQtoSQL je technologie určená k vytvoření mapovacích tříd, které kopírují strukturu databáze a umožňují pracovat s daty jako s objekty.
Stačí přidat novou třídu typu LINQtoSQL classes a po jejím vytvoření se otevře WYSIWYG editor. Pomocí drag and drop lze vytvářet mapovací třídy tabulek, pohledů a vložených procedur z databáze. (Stačí otevřít DB v Server Exploreru. Vložené procedury se vkládají do zavíracího panelu napravo.) Automaticky se také vytvářejí vztahy a constrainty mezi namapovanými objekty.
Základní třída, která poskytuje metody pro přístup k databázi je <váš_název_LINQtoSQL_tříd>DataContext. Její instanci můžete vytvářet buď staticky, nebo pro každý dotaz zvlášť, záleží na okolnostech(lze také použít Singleton). Zmiňovaná třída poskytuje jako svoje property jednotlivé tabulky a jako metody i vložené procedury.
Zásadní metody jsou InsertOnSubmit a DeleteOnSubmit u jednotlivých tabulek - ta slouží pro vkládání/mazání více záznamů. Záznamy se vkládají do bufferu a až po zavolání další důležité metody SubmitChanges na DataContextu jsou všechny nové záznamy vloženy/smazány.
Poněkud odlišný postup se praktikuje při ukládání změn. Pro provedení změny záznamu je postup - načíst data - změnit data - zavolat SubmitChanges a DataContext se už postará o to, aby se všechno uložilo ve správném pořadí a do všech tabulek.
Jednoduché příklady
Select pomocí Extension methods a lambda výrazů
Db je instance DataContextu, SystemUsers je tabulka s uživateli a email je lokální proměnná.
SystemUser foundUser = Db.SystemUsers.SingleOrDefault(p ⇒ p.Email == email);
Velmi jednoduchý dotaz. Extension metoda SingleOrDefault vrátí jeden záznam, nebo defaultní hodnotu dané třídy - v tomto případě null. Je třeba dát pozor na to, že metoda vyvolá vyjímku pokud podmínce odpovídá víc než jeden záznam. Lambda výraz je jednoduchá projekce přes objekty typu SystemUser, kde se porovnává property Email.
Stejný select pomocí linq keywords
Dotazy se dají také skládat pomocí linq keywords, což jsou nová klíčová slova pro C#, jejichž zápis evokuje klasickou TSQL syntax, ale ve skutečnosti jde jen o syntaktický cukr a kompilátor z nich vytvoří extension metody.
var users = from u in Db.SystemUsers
where u.Email == email
select u;
SystemUser foundUser = users.FirstOrDefault();
Vložení záznamů
Vytvořím objekt, vložím, zavolám submit - hotovo.
SystemUser usr1 = new SystemUser
{
email = “usr1@mail.com”,
Nick = “usr1”
};
SystemUser usr2 = new SystemUser
{
email = “usr2@mail.com”,
Nick = “usr2”
};
Db.SystemUsers.InsertOnSubmit(usr1);
Db.SystemUsers.InsertOnSubmit(usr1);
Db.SubmitChanges();
Změna záznamu
Někde na záčátku aplikace načtu objekt.
SystemUser foundUser = Db.SystemUsers.SingleOrDefault(p ⇒ p.Email == email);
Uložím objekt foundUser, třeba do Session. Uživatel chce změnit Nick - zadá nový nick a dá uložit. Vytáhnu objekt foundUser ze Session a změním data.
foundUser.Nick = newNick;
Db.SubmitChanges();
Pokud by měněná property nebyla jen string, ale třeba odkaz na další tabulku, může to vypadat podobně:
foundUser.Roles.Add(new Role{ Name=“Admin” });
Db.SubmitChanges();
Výhoda je v tom, že už se nemusíte starat o to, kde jsou jaké části objektu uloženy a prostě jen zavoláte SubmitChanges.
Optimalizace, modifikace
Pokud je potřeba nějak rozšířit, či upravit vygenerované LINQtoSQL třídy, tak je nejlepší definovat jejich partial část v jiném .cs souboru. Třídy jsou definovány jako partial, takže je lze rozdělit do více souborů. Tento postup je nutný, protože při jakékoliv změně v mapování pomocí WYSIWYG editoru jsou všechny třídy znovu vygenerovány - tj. přepsány.
Existuje několik způsobů jak optimalizovat linq dotazy do databáze. Nejjednodušší je nastavení property Delay Loaded pro jednotlivé proměnné. Pokud máte tabulku obsahující velká data (např. obrázky uložené v DB, nebo dlouhé texty), pak pokud jim nastavíte Delay Loaded na True, bude doopravdy načtena až při pokusu o přístup k této property. Tj. při načtení objektu se načtou všechny sloupce, kromě těch takto označených. V podstatě jde o podobný princip, jako když v selectu neuvedete *, ale jen jednotlivé sloupce, které chcete.
Další optimalizací je, že lze označit property, které se mají načítat zároveň s celým objektem. Nejlepší bude uvést příklad.
Buď při každém vytvoření instance třídy DataContext, nebo přetížením její metody OnCreated:
/// <summary> /// Called when the DataClassesDataContext is created. /// Adds more DataLoadOptions in order to improve the LINQtoSQL performance. /// </summary> partial void OnCreated() { DataLoadOptions opt = new DataLoadOptions(); opt.LoadWith<SystemUser>(su => su.Roles); LoadOptions = opt; }
Tento kousek kódu specifikuje, že při načítání objektu SystemUser se má načíst i jeho kolekce rolí, která je ve skutečnosti v další tabulce. Defaultně by byly role načteny až při prvním přístupu ke kolekci. Což je někdy dobré a někdy nevyhovující chování.