načítání...
nákupní košík
Košík

je prázdný
a
b

Kniha: Microsoft LINQ -- Kompletní průvodce programátora - Marco Russo; Paolo Pialorsi

Microsoft LINQ -- Kompletní průvodce programátora
-15%
sleva

Kniha: Microsoft LINQ -- Kompletní průvodce programátora
Autor: ;

Existuje technologie, jež dokáže získat jakoukoli informaci z jakéhokoli datového zdroje bez ohledu na to, odkud pochází nebo jak se skladuje? Odpověď zní: dotazovací jazyk LINQ. ... (celý popis)
Titul nyní (přelom roku) doručujeme za 9 pracovních dní
Vaše cena s DPH:  990 Kč 842
+
-
rozbalKdy zboží dostanu
28,1
bo za nákup
rozbalVýhodné poštovné: 29Kč
rozbalOsobní odběr zdarma

hodnoceni - 0%hodnoceni - 0%hodnoceni - 0%hodnoceni - 0%hodnoceni - 0%   celkové hodnocení
0 hodnocení + 0 recenzí

Specifikace
Nakladatelství: » Computer press
Médium / forma: Tištěná kniha
Rok vydání: 2009-09-11
Počet stran: 616
Rozměr: 167 x 225 mm
Úprava: 615 stran : ilustrace
Vydání: Vyd. 1.
Název originálu: Programming Microsoft LINQ
Spolupracovali: překlad Jiří Fadrný
Vazba: vázaná s laminovaným potahem
ISBN: 9788025127353
EAN: 9788025127353
Ukázka: » zobrazit ukázku
Popis

Existuje technologie, jež dokáže získat jakoukoli informaci z jakéhokoli datového zdroje bez ohledu na to, odkud pochází nebo jak se skladuje? Odpověď zní: dotazovací jazyk LINQ. Ovládněte i vy novou revoluční technologii společnosti Microsoft, s níž posunete vaše projekty blíže k dokonalosti. Dvojice zkušených vývojářů vás provede možnostmi využití LINQ v praxi od samotných základů až po komplikované projekty. Na¬učíte se pracovat s daty při programování na platformě Microsoft .NET 3.5 a kromě realizace nových projektů získáte potřebné znalosti pro začlenění LINQ do již existujících. Praktické příklady autoři demonstrují v jazycích C# a Visual Basic. Kniha vám mimo jiné ukáže, jak: - Vytvářet dotazy v LINQ - Efektivně zařazovat do dotazů klíčová slova - Rychle a snadno aktualizovat data - Pracovat se stromem výrazů - Vylepšit možnosti LINQ pomocí rozšíření - Využít LINQ ve vícevrstvém řešení - Maximálně využít spolupráce LINQ s dalšími produkty Microsoftu Na adrese http://knihy.cpress.cz/K1695 naleznete veškeré zdrojové kódy příkladů uvedených v knize. O autorech: Paolo Pialorsi je profesionální konzultant a školitel specializující se na vývoj v prostředích Microsoft .NET, XML a webových služeb. Stal se úspěšným autorem několika knih. Marco Russo školí vývojáře pracující v prostředí Microsoft .NET a Microsoft SQL Server. Se spoluautorem této knihy jsou zakladateli společnosti DevLeap, která se zaměřuje na vzdělávání profesionálních programátorů. (kompletní průvodce programátora)

Předmětná hesla
Kniha je zařazena v kategoriích
Recenze a komentáře k titulu
Zatím žádné recenze.


Ukázka / obsah
Přepis ukázky

KAPITOLA 4

LINQ pro SQL:

Dotazování na data

První a nejzřejmější aplikací LINQ je dotazování do externí relační databáze. LINQ

pro SQL je součást projektu LINQ, která nabízí možnost dotazování do relačnídatabáze Microsoft SQL Serveru a také objektový model vycházející z dostupných entit.

Jinými slovy, můžete definovat množinu objektů, které představují tenkou abstraktní

vrstvu nad relačními daty, a do tohoto objektového modelu se dotazovat pomocí

dotazů LINQ, které se ve stroji LINQ pro SQL převádějí na odpovídající dotazy SQL.

LINQ pro SQL podporuje Microsoft SQL Server od verze SQL Serveru 2000 aMicrosoft SQL Server Compact počínaje verzí 3.5.

V LINQ pro SQL lze napsat takovýto prostý dotaz:

var query =

from c in Customers

where c.Country == „USA“

&& c.State == „WA“

select new {c.CustomerID, c.CompanyName, c.City };

Tento dotaz se převádí na dotaz SQL, jenž se pošle do relační databáze:

SELECT CustomerID, CompanyName, City

FROM Customers

WHERE Country = ‘USA‘

AND Region = ‘WA‘

Důležité

Dotazy SQL generované v LINQ, které si budeme ukazovat v této kapitole, jsou

pouze ilustrativní. Microsoft si vyhrazuje právo nezávisle definovat SQLgenerované v LINQ a občas v textu použijeme zjednodušené dotazy. Neměli byste

se tedy na vypisované dotazy SQL spoléhat. V tuto chvíli vás možná napadá několik otázek. Jednak, jak lze psát dotaz LINQ pomocí názvů objektů validovaných kompilátorem? Dále, kdy se generuje z dotazu LINQ dotaz SQL? A za třetí, kdy se dotaz SQL provádí? Abyste poznali odpovědi na tyto otázky, musíte pochopit model entit v LINQ pro SQL a také princip odloženého provádění dotazů.


Část II – LINQ pro relační data116

Entity v LINQ pro SQL

Libovolná externí data je nutné popsat příslušnými metadaty, navázanými na definice tříd.

Každá tabulka musí mít odpovídající třídu s příslušnými atributy. Tato třída odpovídá řádku

dat a popisuje všechny sloupce jako datové členy definovaného typu. Typem může být úplný

nebo částečný popis existující fyzické tabulky, pohledu či výstupu z uložené procedury. Uvnitř

dotazu LINQ pro projekci či filtrování je možné používat pouze popsaná pole. Výpis 4.1ukazuje definici krátké a jednoduché entity.

Důležité

Do svého projektu musíte vložit sestavení System.Data.Linq, abyste mohli používat třídy

a atributy LINQ pro SQL. Atributy používané ve výpisu 4.1 mají své definice ve jmenném

prostoru System.Data.Linq.Mapping.

Výpis 4.1 Definice entity pro LINQ pro SQL

using System.Data.Linq.Mapping;

[Table(Name=“Customers“)]

public class Customer {

[Column] public string CustomerID;

[Column] public string CompanyName;

[Column] public string City;

[Column(Name=“Region“)] public string State;

[Column] public string Country;

} Typ Customer stanovuje obsah řádku a každé pole či vlastnost s atributem Column odpovídá sloupci v relační tabulce. Parametr Name může obsahovat název sloupce, jenž se odlišuje od názvu datového členu. (V našem příkladě odpovídá název State sloupci Region v příslušné tabulce.) Atribut Ta b l e říká, že třída je entitou reprezentující data v databázové tabulce; vlastnost Name může obsahovat název tabulky, který se odlišuje od názvu entity. Pro název třídy se obvykle používá jednotné číslo (jeden řádek) a pro název tabulky (množina řádků) množné číslo. Abyste mohli sestavovat dotazy LINQ pro SQL do dat zákazníků, potřebujete tabulku Customers. Správným způsobem, jak vytvořit takový typ, je použít obecnou třídu Table<T>: Table<Customer> Customers = ...; // ... var query =

from c in Customers

// ...

Poznámka

K vytváření dotazu LINQ do tabulky Customers potřebujete třídu implementující rozhraní IEnumerable<T>, kde jako T bude figurovat typ Customers. Nicméně LINQ pro SQL

potřebuje implementace rozšiřujících metod v jiné podobě, než v jaké je implementuje

LINQ pro objekty, jež jsme viděli v kapitole 3, „LINQ pro objekty“. Z tohoto důvodumusí>


117Kapitola 4 – LINQ pro SQL: Dotazování na data

4

LINQ pro SQL

te použít objekt s rozhraním IQueryable<T>, abyste mohli vytvářet dotazy LINQ pro SQL.

Třída Table<T> rozhraní IQueryable<T> obsahuje. Aby bylo možné vložit rozšíření LINQ

pro SQL, musí být ve zdrojovém kódu příkaz using System.Data.Linq. Pro objekt tabulky Customers je potřeba založit instanci. K tomu potřebujete instanci třídy DataContext, která vytváří most mezi prostředím LINQ a externí relační databází. Principu práce s třídou DataContext je nejpodobnější spojení do databáze – ve skutečnosti je povinným parametrem při zakládání instance třídy DataContext připojovací řetězec či objekt Connection. Metoda GetTable<T> vrací odpovídající objekt typu Table<T> pro zadaný typ: DataContext db = new DataContext(„Database=Northwind“); Table<Customer> Customers = db.GetTable<Customer>();

Poznámka

Třída DataContext interně používá třídu SqlConnection z prostředí ADO.NET. Existující

spojení SqlConnection můžete vložit do konstruktoru třídy DataContext a spojení, které

používá instance třídy DataContext, můžete číst prostřednictvím vlastnosti Connection.

Všechny služby související s databázovým spojením, například používání zásobníku

spojení (connection pooling, standardně zapnuto), jsou dostupné na úrovni spojení

SqlConnection a třída DataContext je přímo neimplementuje. Výpis 4.2 ukazuje výsledný kód, když všechny uvedené prvky spojíte dohromady. Výpis 4.2 Jednoduchý dotaz LINQ pro SQL

DataContext db = new DataContext( ConnectionString );

Table<Customer> Customers = db.GetTable();

var query =

from c in Customers

where c.Country == „USA“

&& c.State == „WA“

select new {c.CustomerID, c.CompanyName, c.City };

foreach( var row in query ) {

Console.WriteLine( row );

}

Proměnná query se inicializuje pomocí dotazovacího výrazu, jenž vytváří strom výrazu.

Strom výrazu představuje obraz výrazu v paměti, a nikoli odkaz na metodu pomocí delegáta.

Když se ve smyčce foreach vypisují data získaná dotazem, používá se strom výrazu kegenerování odpovídajícího dotazu SQL s pomocí všech metadat a informací, které se nacházejí ve

třídách entit a v používané instanci třídy DataContext.

Poznámka

Metoda odloženého provádění, jež se používá v LINQ pro SQL, převádí strom výrazu na

dotaz SQL, jenž je platný v příslušné relační databázi. Dotaz LINQ je funkčněekvivalentní řetězci s dotazem SQL, přinejmenším se dvěma významnými rozdíly. Jednak je dotaz

vázán na objektový model, a nikoli na databázovou strukturu. A dále, jeho reprezentace


Část II – LINQ pro relační data118

je sémanticky logická bez nutnosti používání analyzátoru SQL a bez vazby na určitou

konkrétní verzi jazyka SQL. Strom výrazu je také možné před použitím dynamicky

vytvářet v paměti, což si ukážeme v kapitole 11, „Uvnitř stromů výrazů“. Návratová data z dotazu SQL, přistupující k řádku row ve smyčce foreach, se používají knaplnění promítaného anonymního typu zapsaného za klíčovým slovem select. V této ukázce se nikde nezakládá instance třídy Customer, která se v LINQ používá pouze pro analýzumetadat. Vygenerovaný dotaz SQL si můžete prohlédnout pomocí metody GetCommand třídy DataContext. Ve vlastnosti CommandText navráceného objektu typu DbCommand se nachází vygenerovaný dotaz v jazyce SQL: Console.WriteLine( db.GetCommand( query ).CommandText ); Jednodušší je zavolat pro dotaz LINQ pro SQL metodu ToString: přepsaná metoda To S t r i n g vrací tentýž výsledek jako příkaz GetCommand( query ).CommandText. Console.WriteLine( query ); Jednoduchý dotaz LINQ pro SQL ve výpisu 4.2 generuje následující dotaz SQL: SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[City] FROM [Customers] AS [t0] WHERE ([t0].[Country] = @p0) AND ([t0].[Region] = @p1) Jiným způsobem, jak sledovat všechny příkazy SQL posílané do databáze, je přiřadit hodnotu do vlastnosti Log třídy DataContext: db.Log = Console.Out; V následujících pasážích se podrobněji podíváme na generování tříd entit pro LINQ pro SQL. Externí mapování Mapování mezi entitami LINQ pro SQL a databázovou strukturou je nutné popsatprostřednictvím informací v metadatech. Ve výpisu 4.1 jste viděli atributy v definici entity, které splňovaly toto pravidlo. Ale je také možné použít externí mapovací soubor XML, který bude popisovat třídy entit a nebude tak nutné pracovat s atributy. Mapovací soubor XML vypadá podobně jako následující ukázka: <Database Name=“Northwind“>

<Table Name=“Products“>

<Type Name=“Product“>

<Column Name=“ProductID“ Member=“ProductID“

Storage=“_ProductID“ DbType=“Int NOT NULL IDENTITY“

IsPrimaryKey=“True“ IsDbGenerated=“True“ />

Element Ty p e definuje vztah mezi třídou entity a atribut Member elementu Column udává

odpovídající název členu ve třídě entity pro případ, že by byl odlišný od názvu sloupce

v tabulce. Standardně není atribut Member povinný a předpokládá se, že je stejný jako atribut

Name elementu Column. Tento soubor XML obvykle mívá rozšíření názvu souboru dbml

a generují jej některé nástroje popsané v kapitole 6, „Nástroje LINQ pro SQL“.


119Kapitola 4 – LINQ pro SQL: Dotazování na data

4

LINQ pro SQL

Soubor XML lze načíst pomocí instance třídy XmlMappingSource generované voláním její

statické metody FromXml a předáním této instance do odvozeného konstruktoru třídy

DataContext. Práci s touto syntaxí ukazuje následující příklad:

string path = „Northwind.dbml“;

XmlMappingSource prodMapping =

XmlMappingSource.FromXml(File.ReadAllText(path));

Northwind db = new Northwind(

„Database=Test_Northwind;Trusted_Connection=yes“,

prodMapping

);

Jedním z možných využití této techniky je situace, kdy je potřeba mapovat různé databáze

na specifický datový model. Databáze se mohou lišit v tabulkách a názvech polí (například

lokalizovaná verze databáze). Obecně byste měli o této možnosti uvažovat tehdy, když

potřebujete trochu uvolnit mapovací vazbu mezi třídami entit a fyzickou datovoustrukturou v databázi.

Další informace

Podrobný popis syntaxe XML v souboru .dbml je mimo možnosti této knihy. Syntaxi

popisují soubory LinqToSqlMapping.xsd a DbmlSchema.xsd, jež se nacházejí v adresáři

Microsoft Visual Studio 9.0XmlSchemas ve složce Program Files na vašem počítači,

máte-li nainstalováno Visual Studio 2008. Nemáte-li ani jeden z těchto souborů, můžete

si zkopírovat kód ze stránky dokumentace tohoto produktu na adrese http://msdn2.

microsoft.com/en-us/library/bb386907.aspx a http://msdn2.microsoft.com/en-us/library/

bb399400.aspx. Modelování dat Množina tříd entit, které LINQ pro SQL potřebuje, je tenká abstraktní vrstva nad relačním modelem. Každá třída entity definuje tabulku dat, do níž se lze dotazovat a kterou lze měnit. Instance měněných entit mohou promítat provedené změny do dat v relační databázi. Možnosti aktualizace dat uvidíte v kapitole 5, „LINQ pro SQL: Správa dat“. V následujících odstavcích se naučíte vytvářet datový model pro LINQ pro SQL. DataContext Třída DataContext obstarává komunikaci mezi LINQ a externími relačními datovými zdroji. Každá instance má vlastnost Connection, jež směřuje na relační databázi. Je typu IDbConnection, a proto by neměla být specificky zaměřena na konkrétní databázový produkt. Nicméně implementace LINQ pro SQL podporuje pouze databáze Microsoft SQL Server. Volba mezi konkrétními verzemi SQL Serveru závisí pouze na připojovacím řetězci, předaném do konstruktoru třídy DataContext.


Část II – LINQ pro relační data120

Důležité

Architektura LINQ pro SQL podporuje mnoho poskytovatelů dat, aby bylo možnémapovat různorodé základní relační databáze. Poskytovatel je třída s rozhraním System.

Data.Linq.Provider.IProvider. Toto rozhraní je však deklarováno jako vnitřní a nenízdokumentováno. Microsoft podporuje pouze poskytovatele pro Microsoft SQL Server. .NET

Framework 3.5 podporuje SQL Server 2000 a SQL Server 2005 ve 32bitové i 64bitové

verzi. V čase psaní této knihy je podporován i SQL Server Compact 3.5, avšak pouze

ve 32bitové verzi, protože SQL Server Compact 3.5 pro 64bitové platformy zatím není

dostupný. (Pravděpodobně se dočká podpory v následujícím vydání.) Další verze SQL

Serveru budou patrně rovněž podporovány. Třída DataContext používá informaci v metadatech k mapování fyzické struktury relačních dat, z níž vychází generování kódu SQL. Třídu DataContext lze rovněž použít k volání uložené procedury a k trvalému uložení změn v instancích tříd entit do relační databáze. Třídy pro specializovaný přístup do konkrétní databáze lze ze třídy DataContext odvodit. Takové třídy nabízejí snazší přístup k relačním datům, včetně členů, které představujídostupné tabulky. Pole odkazující se na existující tabulky v databázi lze definovat prostou deklarací bez nutnosti konkrétní inicializace, což dokládá následující kód: public class SampleDb : DataContext {

public SampleDb(IDbConnection connection)

: base( connection ) {}

public SampleDb(string fileOrServerOrConnection)

: base( fileOrServerOrConnection ) {}

public SampleDb(IDbConnection connection, MappingSource mapping)

: base( connection, mapping ) {}

public Table<Customer> Customers;

}

Poznámka

Členy tabulky se inicializují automaticky v základním konstruktoru třídy DataContext,

jenž zkoumá typ za běhu pomocí Reflection, vyhledává příslušné členy a inicializuje je

na základě mapovacích metadat. Třídy entit Třída entity má dvě role. První je poskytovat metadata pro dotazovací stroj LINQ: pro tyto účely se nezakládá instance třídy entity. Druhou rolí je vytvářet úložiště pro data načtená ze zdroje relačních dat, uchovávat případné změny a zařizovat jejich přenos zpět do zdroje relačních dat. Třídu entity tvoří libovolná definice referenčního typu s atributem Ta b l e. Pro tyto účely nelze použít strukturu (hodnotový typ). Atribut Ta b l e může mít parametr Name, který specifikuje název odpovídající tabulky v databázi. Jestliže parametr Name neexistuje, použije sestandardně název třídy: [Table(Name=“Products“)] public class Product { ... }


121Kapitola 4 – LINQ pro SQL: Dotazování na data

4

LINQ pro SQL

Poznámka

I když se běžně používá pojem tabulka, nic vám nebrání použít v parametru Name

namísto tabulky aktualizovatelný pohled. Pohled, jejž aktualizovat nelze, je možné

použít také, přinejmenším do chvíle, dokud se nepokusíte aktualizovat data bez použití

dané třídy entity. Uvnitř třídy entity může být libovolný počet členů libovolného typu. Při definici mapování mezi třídou entity a odpovídající tabulkou v databázi hrají roli pouze datové členy či vlastnosti s atributem Column: [Column] public int ProductID; Třída entity by měla mít jedinečný klíč. Tento klíč je nezbytný pro jedinečnou identitu (více o tématu později), aby bylo možné identifikovat odpovídající řádky v databázových tabulkách a generovat příkazy SQL pro aktualizaci dat. Jestliže nemáte v tabulce primární klíč, lze zakládat instance tříd entit, avšak tyto instance nebudou modifikovatelné. Logická hodnota IsPrimaryKey v atributu Column nastavená na true říká, že sloupec přísluší primárnímu klíči v tabulce. Jestliže se jako primární klíč používá složený klíč, všechny sloupce vytvářející tento primární klíč budou mít ve svých parametrech nastaveno IsPrimaryKey=true: [Column(IsPrimaryKey=true)] public int ProductID; Standardně se sloupec mapuje pomocí stejného názvu, jaký má člen, v němž se používá daný atribut Column. Můžete použít i odlišný název a zadat hodnotu parametru Name. Například následující člen Price odpovídá poli UnitPrice v tabulce v databázi: [Column(Name=“UnitPrice“)] public decimal Price; Chcete-li filtrovat přístup k datům pomocí přístupů ve vlastnostech členů, musíte doparametru Storage zadat příslušný člen základního úložiště. Jestliže vložíte parametr Storage, LINQ pro SQL obejde veřejný přístup k vlastnosti a bude interagovat přímo se základní hodnotou. Pochopení tohoto principu je velice důležité především v situaci, kdy chcete sledovat pouze úpravy prováděné ve vašem kódu, a nikoli operace čtení/zápisu, které provádí prostředí LINQ. V následujícím kódu se přistupuje k vlastnosti ProductName při každé operaci čtení/ zápis ve vašem kódu; při spuštění operace LINQ se provede přímé čtení/zápis do datového členu _ProductName: [Column(Storage=“_ProductName“)] public string ProductName {

get { return this._ProductName; }

set { this.OnPropertyChanging(„ProductName“);

this._ProductName = value;

this.OnPropertyChanged(„ProductName“);

}

}

Vztah mezi relačním typem a typem .NET se zakládá s předpokladem použití výchozího

relačního typu, odpovídajícího použitému typu .NET. Kdykoliv potřebujete stanovit jiný typ,

poslouží vám parametr DBType, který určuje platný typ pomocí platné syntaxe SQL pro daný

relační datový zdroj. Tato vlastnost se používá pouze tehdy, když chcete vytvořit databázové

schéma počínaje definicemi tříd entit (tento proces si popíšeme v kapitole 5):

[Column(DBType=“NVARCHAR(20)“)] public string QuantityPerUnit;


Část II – LINQ pro relační data122

Jestliže hodnotu ve sloupci automaticky generuje databáze (což v SQL Serveru nabízí klíčové

slovo IDENTITY), bude patrně potřeba synchronizovat člen třídy entity s generovanouhodnotou, a to vždy, když vložíte instanci entity do databáze. K tomu musíte nastavit parametr

IsDBGenerated na true a budete muset také příslušným způsobem upravit DBType – například

přidáním modifikátoru IDENTITY pro tabulky SQL Serveru:

[Column(DBType=“INT NOT NULL IDENTITY“,

IsPrimaryKey=true, IsDBGenerated=true)]

public int ProductID;

Stojí za zmínku, že existuje také parametr CanBeNull. Lze v něm stanovit, že daná hodnota může být null, ale je nutné podotknout, že pokud chcete stanovit podobnou podmínku

v databázi vytvořené v LINQ pro SQL, je stále nutné v zadání DBType používat klauzuli NOT

NULL:

[Column(DBType=“INT NOT NULL IDENTITY“, CanBeNull=false,

IsPrimaryKey=true, IsDBGenerated=true)]

public int ProductID;

Další parametry týkající se aktualizace dat jsou AutoSync, Expression, IsVersion a UpdateCheck. Podrobnější výklad parametrů IsDBGenerated, IsVersion a UpdateCheck si uvedeme

v kapitole 5.

Dědičnost entit

Někdy obsahuje jediná tabulka více typů entit. Představte si například seznam kontaktů

– některé mohou být na zákazníky, další na dodavatele a ostatní na zaměstnance společnosti.

Z datového pohledu může mít každá taková entita určitá specifická pole. (Například zákazník

může mít pole pro slevu, které nehraje roli u zaměstnanců a dodavatelů.) Z pohledu obchodní

logiky může každá entita pracovat s odlišnými obchodními pravidly. Nejlepší způsob, jak

modelovat tento druh dat v objektově orientovaném prostředí, je využít dědičnost a vytvořit

hierarchii specializovaných tříd. LINQ pro SQL umožňuje vytvořit množinu tříd odvozených

z jedné základní třídy a mapovat je na tutéž relační tabulku.

Součástí základní třídy hierarchie je atribut InheritanceMapping, jenž obsahuje odpovídající

odvozené třídy vycházející z hodnoty speciálního sloupce diskriminátoru. Parametr Code

obsahuje možnou hodnotu a parametr Ty p určuje odpovídající odvozený typ. Sloupecdiskriminátoru se definuje nastavením parametru IsDiscriminator na true v atributech sloupce.

Výpis 4.3 nabízí ukázku hierarchie založené na tabulce Contacts v ukázkové databázi

Northwind.

Výpis 4.3 Hierarchie tříd založená na kontaktech

[Table(Name=“Contacts“)]

[InheritanceMapping(Code = „Customer“, Type = typeof(CustomerContact))]

[InheritanceMapping(Code = „Supplier“, Type = typeof(SupplierContact))]

[InheritanceMapping(Code = „Shipper“, Type = typeof(ShipperContact))]

[InheritanceMapping(Code = „Employee“, Type = typeof(Contact), IsDefault = true)]

public class Contact {

[Column(IsPrimaryKey=true)] public int ContactID;

[Column(Name=“ContactName“)] public string Name;

[Column] public string Phone;

[Column(IsDiscriminator = true)] public string ContactType;


123Kapitola 4 – LINQ pro SQL: Dotazování na data

4

LINQ pro SQL

}

public class CompanyContact : Contact {

[Column(Name=“CompanyName“)] public string Company;

}

public class CustomerContact : CompanyContact {

}

public class SupplierContact : CompanyContact {

}

public class ShipperContact : CompanyContact {

public string Shipper {

get { return Company; }

set { Company = value; }

}

} Základní třídou hierarchie je Contact. Je-li kontaktem Customer, Supplier nebo Shipper, odpovídající třídy se odvozují z mezičlánku CompanyContact, kde se nachází pole Company odpovídající sloupci CompanyName ve zdrojové tabulce. Přechodová třída CompanyContact je nezbytná, protože se nelze odkazovat na tentýž sloupec (CompanyName) ve více nežjednom poli, i kdyby to bylo v odlišných třídách téže hierarchie. Třída ShipperContact obsahuje vlastnost Shipper, jež pracuje se stejnou hodnotou Company, ale s odlišným sémantickým významem.

Důležité

Tento přístup vyžaduje, abyste veškeré možné datové sloupce v celé hierarchii sloučili

do jediné tabulky. Máte-li běžnou databázi, budete mít patrně data pro různé entity

uložena v samostatných tabulkách. Můžete nadefinovat pohled a s hierarchií entit

v LINQ pro SQL pracovat s jeho pomocí, ale aby bylo možné data aktualizovat, musí být

aktualizovatelný i pohled.

Úroveň abstrakce, kterou umožňuje práce s různými třídami entit v téže hierarchii, dobře

dokládají ukázkové dotazy ve výpisu 4.4. Dotaz queryTyped používá operátor OfType, zatímco

dotaz queryFiltered vychází z běžné podmínky where, která odfiltrovává nezákaznické kontakty.

Výpis 4.4 Dotazy s hierarchií tříd entit

var queryTyped =

from c in contacts.OfType<CustomerContact>()

select c;

var queryFiltered =

from c in contacts

where c is CustomerContact

select c;

foreach( var row in queryTyped ) {

Console.WriteLine( row.Company );

}


Část II – LINQ pro relační data124

// Potřebujeme explicitní přetypování, abychom mohli přistupovat ke členům //

CustomerContact.

foreach( CustomerContact row in queryFiltered ) {

Console.WriteLine( row.Company );

} Dotazy SQL, vygenerované z těchto dotazů LINQ, jsou funkčně totožné s následujícím. (Skutečný dotaz se odlišuje kvůli zobecněnému kódu.) SELECT [t0].[ContactType], [t0].[CompanyName] AS [Company],

[t0].[ContactID], [t0].[ContactName] AS [Name],

[t0].[Phone]

FROM [Contacts] AS [t0]

WHERE [t0].[ContactType] = ‘Customer‘

Rozdíl mezi dotazy queryType a queryFiltered leží v návratovém typu. Dotaz queryTyped

vrací sekvenci instancí třídy CustomerContact, zatímco dotaz queryFiltered vrací sekvenci

základní třídy Contact. V dotazu queryFiltered musíme explicitně přetypovat výsledek na typ

CustomerContact, pokud chceme přistupovat k vlastnosti Company.

Shoda jedinečného objektu

Instance třídy entity uchovává v paměti reprezentaci řádku dat v tabulce. Jestliže se pokusíte

založit dvě různé entity obsahující stejný řádek z téže instance DataContext, dostanete odkaz

na tentýž objekt v paměti. Jinými slovy, shoda objektu (stejné odkazy) zachovává shodu

dat (tentýž řádek v tabulce) pomocí jedinečného klíče entity. LINQ pro SQL zajišťuje, že

se použije tentýž odkaz na objekt, jestliže se zakládá instance entity založená na výsledku

dotazu pocházejícího z téže instance DataContext. Toto ověřování neprobíhá, jestliževytváříte instanci entity samostatně nebo v odlišné instanci DataContext (bez ohledu na skutečný

datový zdroj). Ve výpisu 4.5 vidíte, že c1 a c2 se odkazují na stejnou instanci třídy Contact,

i když pocházejí ze dvou různých dotazů, zatímco c3 je odlišný objekt, i když je jeho obsah

totožný s ostatními.

Poznámka

Jestliže potřebujete znovu načíst data z databáze ve stejné instanci DataContext,musíte použít metodu Refresh třídy DataContext. Více o tomto tématu naleznete v kapitole

5.

Výpis 4.5 Shoda objektů

var queryTyped =

from c in contacts.OfType<CustomerContact>()

orderby c.ContactID

select c;

var queryFiltered =

from c in contacts

where c is CustomerContact

orderby c.ContactID

select c;

Contact c1 = null;

Contact c2 = null;


125Kapitola 4 – LINQ pro SQL: Dotazování na data

4

LINQ pro SQL

foreach( var row in queryTyped.Take(1) ) {

c1 = row;

}

foreach( var row in queryFiltered.Take(1) ) {

c2 = row;

}

Contact c3 = new Contact();

c3.ContactID = c1.ContactID;

c3.ContactType = c1.ContactType;

c3.Name = c1.Name;

c3.Phone = c1.Phone;

Debug.Assert( c1 == c2 ); // stejná instance

Debug.Assert( c1 != c3 ); // odlišné objekty Omezení entit Třídy entit podporují udržování platných vztahů mezi entitami, podobně jako je tomu scizími klíči v běžném relačním prostředí. Ale třídy entit nemohou pracovat se všemi možnými omezeními pro relační tabulky. Neexistují žádné atributy, které by specifikovaly klíče (jedinečná omezení), spouště a ověřovací výrazy, které lze definovat v relační databázi. Tento fakt hraje roli, když začínáte pracovat s daty pomocí tříd entit, protože nelze zaručit, že databáze aktualizovanou hodnotu přijme. (Může se kupříkladu vyskytnout duplicita v jedinečném klíči.) Avšak vzhledem k tomu, že do instancí entit lze načíst pouze části (řádky) celé tabulky, nejsou stejně tyto druhy kontrol možné bez přístupu do relační databáze. Vztahy mezi entitami Vztahy mezi entitami v relačními databázi se modelují na principu cizích klíčů, odkazujících se na primární klíče v určité tabulce. Třídy entit umějí používat stejný princip pomocí atributu Association, který umí popisovat obě strany vztahu jedna-mnoho, vyjádřeného cizím klíčem. EntityRef Začněme principem vyhledávání (lookup), což je typická operace používaná ke zjištění zákazníka souvisejícího s jednou objednávkou. Na vyhledávání lze nahlížet jako na přímý překlad do modelu entit se vztahem prostřednictvím cizího klíče mezi sloupcem CustomerID v tabulce Orders a primárním klíčem tabulky Customers. V našem modelu entit bude mít třída entity Order atribut Association a příslušnou informaci bude uchovávat ve členu EntityRef<Customer> (s názvem _Customer), jenž umožňuje odložené načítání odkazu, což uvidíte zakrátko. Definici tohoto vztahu vidíte ve výpisu 4.6. Výpis 4.6 Association EntityRef

[Table(Name=“Orders“)]

public class Order {

[Column(IsPrimaryKey=true)] public int OrderID;

[Column] private string CustomerID;

[Column] public DateTime? OrderDate;

[Association(Storage=“_Customer“, ThisKey=“CustomerID“, IsForeignKey=true)]

public Customer Customer {

get { return this._Customer.Entity; }

set { this._Customer.Entity = value; }

}


Část II – LINQ pro relační data126

private EntityRef<Customer> _Customer;

}

Je zjevné, že sloupec CustomerID musí být definován ve třídě Order, neboť v opačném příadě by nebylo možné získat souvisejícího zákazníka. Parametr IsForeignKey říká, že jsme na

podřízené straně vztahunadřízenýodřízený, a parametr ThisKey atributu Association určuje

sloupec „cizího klíče“ (což bude v případě, že se klíč skládá z více sloupců, seznam oddělený

čárkami), který slouží ke stanovení vztahu mezi entitami. Chcete-li tento detail skrýt vevlastnostech entity, můžete daný sloupec deklarovat jako soukromý, stejně jako ve dříve uvedené

třídě Order.

Poznámka

Atribut Association má ještě další dva parametry. Jedním je IsUnique, jenž musí být

nastaven na true, jestliže je v cizím klíči rovněž požadována jedinečnost. V takovém

případě je vztah s nadřízenou tabulkou typu jedna-jedna namísto mnoho-jedna.

Druhým parametrem je Name a používá se pouze k definici názvu omezení, pakliže se

má z metadat pomocí metody DataContext.CreateDatabase generovat databáze, což si

ukážeme v kapitole 5. Pomocí třídy Order můžete v dotazu LINQ zadat vlastnost Customer do filtru, aniž by bylo nutné psát spojení mezi entitami Customer a Order. V následujícím dotazu se používá člen Country navázaného zákazníka, čímž se vyfiltrují objednávky pocházející od zákazníků v konkrétní zemi: Table<Order> Orders = db.GetTable(); var query =

from o in Orders

where o.Customer.Country == „USA“

select o.OrderID;

Uvedený dotaz se přeloží do příkazu SQL JOIN následujícím způsobem:

SELECT [t0].[OrderID]

FROM [Orders] AS [t0]

LEFT JOIN [Customers] AS [t1]

ON [t1].[CustomerID] = [t0].[CustomerID]

WHERE [t1].[Country] = „USA“

Až dosud jsme používali vztahy mezi entitami pouze kvůli jejich metadatům při vytváření

dotazů LINQ. Když dojde k založení instance třídy entity, pracuje odkaz na jinou entitu

(například výše uvedená vlastnost Customer) pomocí techniky zvané odložené načítání.

Instance související entity Customer se nezaloží a nenačte z databáze do paměti až do chvíle,

kdy dojde k přístupu k ní v režimu pro čtení či zápis.

Poznámka

EntityRef<T> je třída obálky, jejíž instance se zakládá v objektu kontejneru (třídaodvozená ze třídy DataContext) a poskytuje platný odkaz pro veškerý přístup k entitě, na

niž se odkazujeme. Každá operace čtení i zápisu se filtruje v blocích vlastnosti get a set,

které spouštějí dotaz pro načítání z databáze při prvním přístupu k této entitě, pokud

již není v paměti.


127Kapitola 4 – LINQ pro SQL: Dotazování na data

4

LINQ pro SQL

Jinými slovy, ke generování dotazu SQL, který naplní entitu navázanou na objekt Customer

při práci s vlastností Country, vám poslouží následující kód:

var query =

from o in Orders

where o.OrderID == 10528

select o;

foreach( var row in query ) {

Console.WriteLine( row.Customer.Country );

}

Proces přístupu k vlastnosti Customer zahrnuje ověření, zdali je související entita Customer

v aktuálním prostředí DataContext již načtena do paměti. Jestliže je, přistupuje se k této

entitě, v opačném případě se provede následující dotaz SQL, do paměti se načte odpovídající

entita Customer a poté dojde k přístupu k ní:

SELECT [t0].[Country], [t0].[CustomerID], [t0].[CompanyName]

FROM [Customers] AS [t0]

WHERE [t0].[CustomerID] = „GREAL“

Řetězec GREAL je hodnota CustomerID pro objednávku 10528. Jak vidíte, příkaz SELECT

se dotazuje do všech sloupců deklarovaných v entitě Customer, i když se ve výrazu, který

vyžaduje entitu Customer, nepoužívají. (V tomto případě se prováděný kód nikdyneodkazoval na člen CompanyName.)

EntitySet

Na druhé straně vztahu stojí tabulka, na niž se odkazuje jiná tabulka prostřednictvím svého

primárního klíče. I když v relačním modelu jde o implicitní důsledek omezení cizím klíčem,

v modelu entit je potřeba tento vztah explicitně definovat. Jestliže se na tabulku Customers

odkazuje tabulka Orders, lze ve třídě Customer vytvořit vlastnost Orders, která představuje

množinu entit Order souvisejících s příslušným zákazníkem. Vztah se zadává pomocíinstance třídy obálky EntitySet<Order> nad sekvencí souvisejících objednávek. Typ EntitySet<T>

lze přímo zveřejnit, což je případ kódu ve výpisu 4.7. Zde parametr OtherKey atributu

Association udává název členu souvisejícího typu (Order), který určuje vazbu mezi třídou

Customer a množinou entit Order.

Výpis 4.7 Association EntitySet (viditelné navenek)

[Table(Name=“Customers“)]

public class Customer {

[Column(IsPrimaryKey=true)] public string CustomerID;

[Column] public string CompanyName;

[Column] public string Country;

[Association(OtherKey=“CustomerID“)]

public EntitySet<Order> Orders;

} Můžete se rovněž rozhodnout zveřejnit objednávky jako vlastnost, což ukazuje výpis 4.8. V tomto případě stanovuje parametr Storage atributu Association množinu EntitySet<T> jako fyzické úložiště. Můžete se rovněž rozhodnout zviditelnit vně třídy Customer pouze kolekci ICollection<Order> namísto množiny EntitySet<Order>, není to však běžná praxe.


Část II – LINQ pro relační data128

Výpis 4.8 Association EntitySet (skryté)

public class Customer {

[Column(IsPrimaryKey=true)] public string CustomerID;

[Column] public string CompanyName;

[Column] public string Country;

private EntitySet<Order> _Orders;

[Association(OtherKey=“CustomerID“, Storage=“_Orders“)]

public EntitySet<Order> Orders {

get { return this._Orders; }

set { this._Orders.Assign(value); }

}

public Customer() {

this._Orders = new EntitySet<Order>();

}

} V obou modelech deklarace vztahu můžete použít třídu Customer v dotazu LINQ a přistuovat k souvisejícím entitám Order, aniž by bylo nutné psát spojení. Stačí použít vlastnost Orders. Následující dotaz vrací jména zákazníků, kteří učinili více než 20 objednávek: Table<Customer> Customers = db.GetTable(); var query =

from c in Customers

where c.Orders.Count > 20

select c.CompanyName;

Předchozí dotaz LINQ se přeloží do dotazu SQL podobného tomuto:

SELECT [t0].[CompanyName]

FROM [Customers] AS [t0]

WHERE ( SELECT COUNT(*)

FROM [Orders] AS [t1]

WHERE [t1].[CustomerID] = [t0].[CustomerID]

) > 20

V tomto případě se nevytvářejí žádné instance entity Order. Vlastnost Orders slouží pouze

jako zdroj metadat, s jehož pomocí se generuje požadovaný dotaz SQL. Jestliže vrátíte

z dotazu LINQ entitu Customer, můžete podle potřeby přistupovat k objednávkám určitého

zákazníka:

var query =

from c in Customers

where c.Orders.Count > 20

select c;

foreach( var row in query ) {

Console.WriteLine( row.CompanyName );

foreach( var order in row.Orders ) {

Console.WriteLine( order.OrderID );

}

}

V předcházejícím kódu využíváme odložené načítání. Pokaždé, když přistupujete k vlastnosti Orders určitého zákazníka poprvé (což ukazuje zvýrazněný kód), do databáze se pošle

dotaz podobný následujícímu (který používá @p0 jako parametr pro filtrování hodnot

CustomerID):


129Kapitola 4 – LINQ pro SQL: Dotazování na data

4

LINQ pro SQL

SELECT [t0].[OrderID], [t0].[CustomerID]

FROM [Orders] AS [t0]

WHERE [t0].[CustomerID] = @p0

Chcete-li načíst veškeré objednávky pro všechny zákazníky do paměti jedním dotazem do

databáze, musíte si namísto odloženého načítání vyžádat okamžité načítání. Máte pro to

dvě možnosti. První přístup ukazuje výpis 4.9, kde vynutíte práci s množinou entit pomocí

instance třídy DataLoadOptions a zavoláním její metody LoadWith<T>.

Výpis 4.9 Použití třídy DataLoadOptions a metody LoadWith<T>

DataContext db = new DataContext( ConnectionString );

Table<Customer> Customers = db.GetTable();

DataLoadOptions loadOptions = new DataLoadOptions();

loadOptions.LoadWith<Customer>( c => c.Orders );

db.LoadOptions = loadOptions;

var query =

from c in Customers

where c.Orders.Count > 20

select c;

Druhou možností je výběr nové entity, která explicitně obsahuje vlastnost Orders pro daného

zákazníka:

var query =

from c in Customers

where c.Orders.Count > 20

select new { c.CompanyName, c.Orders }; Tyto dotazy LINQ posílají dotaz SQL do databáze a načítají všechny zákazníky, kteří učinili více než 20 objednávek, včetně úplného seznamu objednávek pro každého zákazníka. Příslušný dotaz SQL by mohl vypadat asi jako následující kód: SELECT [t0].[CompanyName], [t1].[OrderID], [t1].[CustomerID], (

SELECT COUNT(*)

FROM [Orders] AS [t3]

WHERE [t3].[CustomerID] = [t0].[CustomerID]

) AS [value] FROM [Customers] AS [t0] LEFT OUTER JOIN [Orders] AS [t1] ON [t1].[CustomerID] = [t0].[CustomerID] WHERE (

SELECT COUNT(*)

FROM [Orders] AS [t2]

WHERE [t2].[CustomerID] = [t0].[CustomerID]

) > 20 ORDER BY [t0].[CustomerID], [t1].[OrderID]

Poznámka

Všimněte si, že zde máme jediný dotaz SQL a stroj LINQ pro SQL výsledky rozděluje

– vybírá odlišné entity (Customers a Orders). Díky zachování řazení výsledků podle

CustomerID může LINQ pro SQL vytvářet entity a vztahy v paměti rychleji.

Poddotaz získaný ze vztahu lze filtrovat. Předpokládejme, že chcete vidět pouze zákazníky,

kteří v roce 1997 uskutečnili nejméně pět objednávek, a chcete načíst a vidět pouze tyto


Část II – LINQ pro relační data130

objednávky. K tomu vám poslouží metoda AssociateWith<T> třídy DataLoadOptions, což

demonstruje výpis 4.10.

Výpis 4.10 Použití třídy DataLoadOptions a metody AssociateWith<T>

DataLoadOptions loadOptions = new DataLoadOptions();

loadOptions.AssociateWith<Customer>(

c => from o in c.Orders

where o.OrderDate.Value.Year == 1997

select o);

db.LoadOptions = loadOptions;

var query =

from c in Customers

where c.Orders.Count > 5

select c; Dozajista oceníte, že filtrovací podmínka v C# (o.OrderDate.Value.Yer == 1997) se překládá na následující výraz SQL: (DATEPART(Year, [t2].[OrderDate]) = 1997) Metoda AssociateWith<T> může také řídit počáteční řazení kolekce. Stačí k tomu přidat podmínku řazení do dotazu předávaného do parametru metody AssociateWith<T>.Chcete-li například načíst objednávky pro každého zákazníka počínaje nejnovějšími, přidejte řádek orderby, zvýrazněný v následujícím kódu: loadOptions.AssociateWith<Customer>(

c => from o in c.Orders

where o.OrderDate.Value.Year == 1997

orderby o.OrderDate descending

select o);

Samotné použití metody AssociateWith<T> nezaručuje okamžité načítání. Chcete-li okamžité

načítání i filtrování pomocí vztahu, musíte zavolat metody LoadWith<T> i AssociateWith<T>.

Na pořadí těchto volání nezáleží. Můžete například napsat takovýto kód:

DataLoadOptions loadOptions = new DataLoadOptions();

loadOptions.AssociateWith<Customer>(

c => from o in c.Orders

where o.OrderDate.Value.Year == 1997

select o);

loadOptions.LoadWith<Customer>( c => c.Orders );

db.LoadOptions = loadOptions;

Načítání veškerých dat do paměti pomocí jediného dotazu může být lepší v situaci, kdy jistě

víte, že budete přistupovat ke všem načteným datům, protože vás méně zdrží zpoždění při

načítání. Ale uvedená technika spotřebuje více paměti a síťové kapacity než při typickém

náhodném přístupu ke grafu entit. Při rozhodování o dotazech do svého datového modelu

mějte tyto detaily na mysli.

Konzistence grafu

Vztahy mezi entitami jsou obousměrné – dojde-li k aktualizaci na jedné straně, druhou

stranu je potřeba udržet v synchronizaci. LINQ pro SQL neprovádí tento druh synchronizace automaticky a je potřeba jej zařídit při implementaci tříd entit. LINQ pro SQL nabízí

implementační postup, který používají i nástroje pro generování kódu jako SQLMetal, jenž

je součástí sady Microsoft .NET 3.5 Software Development Kit (SDK), či generátor tříd pro


131Kapitola 4 – LINQ pro SQL: Dotazování na data

4

LINQ pro SQL

LINQ pro SQL, který přináleží do Visual Studia 2008. Oba tyto nástroje si popíšeme vkapitole 6. Uvedený postup vychází ze třídy EntitySet<T> na jedné straně a ze složitého bloku set

na straně druhé. Zajímají-li vás implementační podrobnosti tohoto postupu, podívejte se na

kód generovaný těmito nástroji.

Upozornění na změny

V kapitole 5 uvidíte, že LINQ pro SQL umí sledovat změny v entitách a posílat příslušné změny

do databáze. Tento proces probíhá automaticky prostřednictvím algoritmu, který srovnává

obsah objektu s jeho původními hodnotami, což vyžaduje kopii každého sledovanéhoobjektu. Spotřeba paměti může být vysoká, ale lze ji optimalizovat, pokud se do služby sledování

změn zapojí i entity a dají vědět, když dojde v objektu ke změně.

Implementace upozorňování na změny vyžaduje, aby příslušná entita zveřejnila veškerá svá

data ve vlastnostech v rozhraní System.ComponentModel.INotifyPropertyChanging. Každý

blok set ve vlastnosti musí volat metodu PropertyChanging třídy DataContext. Další detaily

naleznete v dokumentaci produktu. Strojově generovaný kód entit (napříkladprostřednictvím programu SQLMetal či ve Visual Studiu 2008) již toto chování obsahuje. Srovnání relačního modelu a hierarchického modelu Model entit, používaný v LINQ pro SQL, definuje množinu objektů, které mapují databázové tabulky na objekty, jež lze používat a obsluhovat v dotazech LINQ. Výsledný modelpředstavuje změnu v přístupu, kterou jsme si ukázali při popisu vztahů mezi entitami. Posunuli jsme se od relačního modelu (tabulky v databázi) k hierarchickému či grafovému modelu (objekty v paměti). Hierarchický/grafový model představuje přirozený způsob práce s objekty v programunapsaném v jazycích C# či Microsoft Visual Basic. Když se pokusíte přeložit existující dotaz SQL do dotazu LINQ, narazíte na významnou principiální překážku. V LINQ můžete napsat dotaz používající spojení mezi samostatnými entitami, podobně jako v SQL. Ale lze také napsat dotaz využívající existující vztahy mezi entitami, což jsme dělali pomocí vztahů EntitySet a EntityRef.

Důležité

Pamatujte si, že SQL nepoužívá při dotazech na data vztahy mezi entitami. Tyto vztahy

existují pouze kvůli definici podmínek datové integrity. LINQ neobsahuje princip referenční integrity, ale využívá vztahy pro definici možných navigačních cest k datům.

Dotazování na data

Dotaz LINQ pro SQL se posílá do databáze pouze tehdy, když program potřebuje načítat data.

Například následující smyčka foreach prochází řádky navrácené z tabulky:

var query =

from c in Customers

where c.Country == „USA“

select c.CompanyName;


Část II – LINQ pro relační data132

foreach( var company in query ) {

Console.WriteLine( company );

}

Kód generovaný příkazem foreach je totožný s následujícím kódem. Přesný okamžikprovádění dotazu odpovídá volání metody GetEnumerator:

// GetEnumerator posílá dotaz do databáze

IEnumerator<string> enumerator = query.GetEnumerator();

while (enumerator.MoveNext()) {

Console.WriteLine( enumerator.Current );

}

Více smyček foreach jednom dotazu znamená odpovídající počet volání metody GetEnumerator

a tedy odpovídající počet opakovaného provádění téhož dotazu. Chcete-li procházet tatáž

data vícekrát, dáte patrně přednost uložení dat do mezipaměti. Pomocí příkazů To L i s t či

To Ar r a y převedete výsledky dotazu na seznam či pole. Když zavoláte tyto metody, pošle se do

databáze dotaz SQL:

// ToList() posílá dotaz do databáze var companyNames = query.ToList();

Posílat dotaz do databáze vícekrát je potřeba, když s dotazem LINQ mezi iteracemimanipulujete. Můžete mít kupříkladu interaktivní uživatelské rozhraní, které uživateli umožňuje přidat

pro každý průchod daty nový filtr. Ve výpisu 4.11 zobrazuje metoda DisplayTop pouze několik

prvních řádků výsledku; manipulace s dotazem mezi voláními metody DisplayTop simuluje

uživatelský zásah, který vede vždy k nové podmínce ve filtru.

Více informací

Výpis 4.11 ukazuje velmi prostou techniku manipulace s dotazem, která přidává další

omezující podmínky k existujícímu dotazu, reprezentovanému objektem IQueryable<T>.

Kapitola 11 popisuje metody, jak dynamicky vystavět strom dotazu pružnějšímzpůsobem.

Výpis 4.11 Manipulace s dotazem

static void QueryManipulation() {

DataContext db = new DataContext( ConnectionString );

Table<Customer> Customers = db.GetTable();

db.Log = Console.Out;

// všichni zákazníci

var query =

from c in Customers

select new {c.CompanyName, c.State, c.Country };

DisplayTop( query, 10 );

// Uživatel přidává

// k předchozímu dotazu filtr.

// zákazníci z USA

query =

from c in query

where c.Country == „USA“

select c;


133Kapitola 4 – LINQ pro SQL: Dotazování na data

4

LINQ pro SQL

DisplayTop( query, 10 );

// Uživatel přidává k předchozímu

// dotazu další filtr.

// zákazníci z USA, Washington

query =

from c in query

where c.State == „WA“

select c;

DisplayTop( query, 10 );

}

static void DisplayTop<T>( IQueryable query, int rows ) {

foreach( var row in query.Take(rows)) {

Console.WriteLine( row );

}

}

Důležité

V předchozí ukázce jsme jako parametr metody DisplayTop použili typ IQueryable<T>.

Kdybychom namísto toho použili typ IEnumerable<T>, výsledky by vypadaly stejně,

ale dotaz poslaný do databáze by neobsahoval klauzuli TOP (řádky), která filtruje data

přímo v databázi. Při práci s typem IEnumerable<T> se používá odlišná množina rozšiřujících metod k rozřešení operátoru Ta ke bez generování nového stromu výrazu.

Popis rozdílů mezi typy IEnumerable<T> a IQueryable<T> naleznete v kapitole 2, „Základy

syntaxe LINQ“.

Běžný dotaz, používaný pro přístup k databázi, je načítání jediného řádku z tabulky, kdydefinujeme podmínku zaručující jedinečnost, kupříkladu pomocí klíče záznamu. Typický dotaz

vypadá takto:

var query =

from c in db.Customers

where c.CustomerID == „ANATR“

select c;

var enumerator = query.GetEnumerator();

if (enumerator.MoveNext()) {

var customer = enumerator.Current;

Console.WriteLine( „{0} {1}“, customer.CustomerID, customer.CompanyName );

}

V tomto případě by bylo kratší a jasnější zapsat svůj záměr pomocí operátoru Single. Předchozí

dotaz lze přepsat do této kompaktnější formy:

var customer = db.Customers.Single( c => c.CustomerID == „ANATR“ );

Console.WriteLine( „{0} {1}“, customer.CustomerID, customer.CompanyName );

Je však důležité si uvědomit, že volání Single má odlišnou sémantiku než předchozí,ekvivalentní dotaz. Volání operátoru Single generuje dotaz do databáze pouze tehdy, jestližepožadovaná entita (v tomto případě objekt typu Customer s CustomerID rovno ANATR) již není

v paměti. Chcete-li načíst data z databáze, musíte zavolat metodu DataContext.Refresh:

db.Refresh(RefreshMode.OverwriteCurrentValues, customer);

Více informací o životním cyklu entity naleznete v kapitole 5.


Část II – LINQ pro relační data134

Projekce

Transformace ze stromu výrazů na dotaz SQL vyžaduje úplné pochopení dotazovacíchoperací poslaných do stroje LINQ pro SQL. Tato transformace ovlivňuje použití inicializátorů

objektů. K projekcím vám poslouží klíčové slovo select, což ukazuje následující příklad:

var query =

from c in Customers

where c.Country == „USA“

select new {c.CustomerID, Name = c.CompanyName.ToUpper()} into r

orderby r.Name

select r; Celý dotaz LINQ se přeloží na tento příkaz SQL: SELECT [t1].[CustomerID], [t1].[value] AS [Name] FROM ( SELECT [t0].[CustomerID],

UPPER([t0].[CompanyName]) AS [value],

[t0].[Country]

FROM [Customers] AS [t0]

) AS [t1]

WHERE [t1].[Country] = „USA“

ORDER BY [t1].[value]

Jak vidíte, metoda To Up p e r byla přeložena na volání funkce T-SQL UPPER. Stroj LINQ pro

SQL musí proto velmi dobře znát význam veškerých operací ve stromu výrazů. Podívejte se

na tento dotaz:

var queryBad =

from c in Customers

where c.Country == „USA“

select new CustomerData( c.CustomerID, c.CompanyName.ToUpper()) into r

orderby r.Name

select r; V tomto případě voláme konstruktor typu CustomerData, který může dělat cokoliv, co zvládne kousek kódu v převodním jazyce (Intermediate Language, IL). Jinými slovy, neexistuje žádná jiná sémantika volání konstruktoru než počáteční přiřazení vytvořené instance. Následkem je skutečnost, že LINQ pro SQL nemůže tuto syntaxi korektně přeložit na ekvivalentní kód SQL a při spuštění tohoto dotazu vypíše výjimku. Parametrizovaný konstruktor je však možné bezpečně použít v závěrečné projekci dotazu, což dokládá následující ukázka: var queryParamConstructor =

from c in Customers

where c.Country == „USA“

orderby c.CompanyName

select new CustomerData( c.CustomerID, c.CompanyName.ToUpper() ); Pokud pouze potřebujete inicializovat objekt, použijte namísto parametrizovanéhokonstruktoru inicializátory objektu, podobně jako v dalším dotazu: var queryGood =

from c in Customers

where c.Country == „USA“

select new CustomerData { CustomerID = c.CustomerID,

Name = c.CompanyName.ToUpper() } into r

orderby r.Name

select r;


135Kapitola 4 – LINQ pro SQL: Dotazování na data

4

LINQ pro SQL

Důležité

Pro kódování projekcí v LINQ pro SQL vždy používejte inicializátory objektu.

Parametrizované konstruktory používejte pouze pro závěrečnou projekci v dotazu.

Uložené procedury a uživatelské funkce

Přístup k datům pomocí uložených procedur a uživatelských funkcí (UDF) vyžaduje definici

odpovídajících metod s atributy. Umožňuje to psát dotazy LINQ se silnou typovou kontrolou.

Z pohledu LINQ není rozdíl v tom, je-li uložená procedura či UDF napsána v jazyce T-SQL

či SQLCLR, musíte však znát určité podrobnosti, máte-li zvládnout rozdíly mezi uloženými

procedurami a uživatelskými funkcemi.

Poznámka

Když uvážíme, že mnoho z vás bude automaticky vytvářet specializované třídyodvozené ze třídy DataContext, soustředíme se na nejvýznamnější principy, které potřebujete

znát pro efektivní používání těchto objektů. Chcete-li ručně vytvářet tyto obálky,

podívejte se do dokumentace produktu, kde naleznete podrobný seznam atributů

a jejich parametrů.

VLožené procedury

Podívejte se na vloženou proceduru Customers by City:

CREATE PROCEDURE [dbo].[Customers By City]( @param1 NVARCHAR(20) )

AS BEGIN

SET NOCOUNT ON;

SELECT CustomerID, ContactName, CompanyName, City

FROM Customers AS c

WHERE c.City = @param1

END

Můžete definovat metodu s atributem Function, která volá tuto vloženou proceduru pomocí

metody ExecuteMethodCall třídy DataContext. Ve výpisu 4.12 nadefinujeme CustomerByCity

jako člen třídy odvozené ze třídy DataContext:

Výpis 4.12 Deklarace uložené procedury

class SampleDb : DataContext {

// ...

[Function(Name = „Customers by City“, IsComposable = false)]

public ISingleResult<CustomerInfo> CustomersByCity(string param1) {

IExecuteResult executeResult =

this.ExecuteMethodCall(

this,

(MethodInfo) (MethodInfo.GetCurrentMethod()),

param1);

ISingleResult<CustomerInfo> result =

(ISingleResult<CustomerInfo>) executeResult.ReturnValue;

return result;

}

}


Část II – LINQ pro relační data136

Metoda ExecuteMethodCall má následující deklaraci:

IExecuteResult ExecuteMethodCall( object instance,

MethodInfo methodInfo,

params object[] parameters)

Prvním parametrem metody je instance (která není nutná, voláte-li statickou metodu).

Druhým parametrem je popis metadat volané metody, který lze získat skrze Reflection, jako

ve výpisu 4.12. Třetím parametrem je pole s parametry předávanými do volané metody.

Metoda CustomersByCity vrací instanci ISingleResult<CustomerInfo>, která obsahujerozhraní IEnumerable<CustomerInfo> a lze ji vypsat ve smyčce foreach, jako je například tato:

SampleDb db = new SampleDb( ConnectionString );

foreach( var row in db.CustomersByCity( „London“ )) {

Console.WriteLine( „{0} {1}“, row.CustomerID, row.CompanyName );

}

Z výpisu 4.12 vidíme, že jsme mohli přistupovat k rozhraní IExecuteResult, navrácenému

metodou ExecuteMetodCall, a získat tak potřebné výsledky. Uvedený postup vyžaduje

další výklad. Tentýž atribut Function používáme pro metodu tvořící obálku uložené procedury i UDF. Rozlišující faktor mezi těmito dvěma konstrukcemi představuje parametr

IsComposable atributu Function: je-li nastaven na false, následná metoda tvoří obálku uložené

procedury, je-li roven true, bude se volat uživatelská funkce.

Poznámka

Název IsComposable se odkazuje na kompozitní chování uživatelských funkcí vdotazovacím výrazu. Ukázku uvidíte, až si budeme v další části kapitoly popisovat mapování

uživatelských funkcí. Rozhraní IExecuteResult má prostou definici: public interface IExecuteResult : IDisposable {

object GetParameterValue(int parameterIndex);

object ReturnValue { get; }

}

Metoda GetParameterValue umožňuje přistupovat k výstupním parametrům vloženéprocedury. Tento výsledek je nutné převést na správný typ a do parametru parameterIndex vložit

ordinální pozici výstupního parametru.

Vlastnost ReturnValue, určená pouze pro čtení, slouží k přístupu k návratové hodnotě

vložené procedury či UDF. Skalární návratovou hodnotu lze používat po převodu na správný

typ: vložená procedura vždy vrací celé číslo, kdežto typ uživatelské funkce se může různit.

Ale je-li výsledkem tabulka, používá se pro přístup k jediné výsledkové množině rozhraní

ISingleResult<T>, zatímco pro přístup k vícenásobným výsledkovým množinám slouží rozhraní IMultipleResults.

Vždy je potřeba znát metadata všech možných návratových výsledkových množin a nagenerická rozhraní, sloužící k návratu dat, aplikovat správný typ. ISingleResult<T> je jednoduchá

obálka rozhraní IEnumerable<T>, které rovněž obsahuje rozhraní IFunctionResult, jež má

vlastnost ReturnValue určenou pouze pro čtení, která funguje jako vlastnost IExecuteResult.

ReturnValue, kterou jsme již viděli



       
Knihkupectví Knihy.ABZ.cz - online prodej | ABZ Knihy, a.s.
ABZ knihy, a.s.
 
 
 

Knihy.ABZ.cz - knihkupectví online -  © 2004-2018 - ABZ ABZ knihy, a.s. TOPlist