LINQ: Dotaz Language-Integrated .NET
Don Box, Anders Hejlsberg
Únor 2007
Platí pro:
Název orcas v editoru Visual Studio Code
.Net Framework 3.5
Shrnutí: Dotazovací zařízení pro obecné účely přidané do rozhraní .NET Framework platí pro všechny zdroje informací, nejen relační data nebo data XML. Toto zařízení se nazývá .NET Language-Integrated Query (LINQ). (32 tištěných stránek)
Obsah
Dotaz Language-Integrated .NET
Začínáme se standardními operátory dotazů
Jazykové funkce podporující projekt LINQ
Standardnější operátory dotazů
Syntaxe dotazů
LINQ to SQL: Integrace SQL
LINQ to XML: Integrace XML
Souhrn
Dotaz Language-Integrated .NET
Po dvou desetiletích dosáhlo odvětví stabilního bodu ve vývoji objektově orientovaných programovacích technologií (OO). Programátoři nyní berou jako samozřejmost funkce, jako jsou třídy, objekty a metody. Při pohledu na současnou a novou generaci technologií se ukázalo, že další velkou výzvou při programování technologií je snížení složitosti přístupu k informacím, které nejsou nativně definované technologií OO, a jejich integrace. Dva nejběžnější zdroje informací, které nejsou OO, jsou relační databáze a XML.
Místo toho, abychom do programovacích jazyků a modulu runtime přidali relační funkce nebo funkce specifické pro XML, jsme v projektu LINQ použili obecnější přístup a do rozhraní .NET Framework přidáváme zařízení pro obecné dotazy, které se vztahují na všechny zdroje informací, nejen relační data nebo data XML. Toto zařízení se nazývá .NET Language-Integrated Query (LINQ).
Termín dotaz integrovaný do jazyka používáme k označení, že dotaz je integrovanou funkcí primárních programovacích jazyků vývojáře (například Visual C#, Visual Basic). Dotaz integrovaný s jazykem umožňuje výrazům dotazů využívat výhod bohatých metadat, kontroly syntaxe kompilace, statického psaní a technologie IntelliSense, která byla dříve k dispozici pouze imperativnímu kódu. Dotaz integrovaný s jazykem také umožňuje použít jedno obecné deklarativní dotazovací zařízení pro všechny informace v paměti, nejen informace z externích zdrojů.
.NET Language-Integrated Query definuje sadu standardních dotazovacích operátorů pro obecné účely, které umožňují vyjádřit operace procházení, filtrování a projekce přímým, ale deklarativním způsobem v libovolném objektu . Programovací jazyk založený na technologii NET. Standardní operátory dotazů umožňují použít dotazy na jakýkoli zdroj informací založený na IEnumerable<T>. LINQ umožňuje třetím stranám rozšířit sadu standardních operátorů dotazů o nové operátory specifické pro doménu, které jsou vhodné pro cílovou doménu nebo technologii. Ještě důležitější je, že třetí strany také mohou nahradit standardní operátory dotazů vlastními implementacemi, které poskytují další služby, jako je vzdálené vyhodnocení, překlad dotazů, optimalizace atd. Díky dodržování konvencí vzoru LINQ mají tyto implementace stejnou jazykskou integraci a podporu nástrojů jako standardní operátory dotazů.
Rozšiřitelnost architektury dotazu se používá v samotném projektu LINQ k poskytování implementací, které pracují s daty XML i SQL. Operátory dotazů přes XML (LINQ to XML) používají efektivní a snadno použitelné zařízení XML, které poskytuje funkce XPath/XQuery v programovacím jazyce hostitele. Operátory dotazů nad relačními daty (LINQ to SQL) vycházejí z integrace definic schémat založených na SQL do systému typů CLR (Common Language Runtime). Tato integrace poskytuje silné překlepy na relační data a přitom zachovává výrazovou sílu relačního modelu a výkon vyhodnocení dotazů přímo v podkladovém úložišti.
Začínáme se standardními operátory dotazů
Abychom viděli, jak fungují dotazy integrované do jazyka, začneme jednoduchým programem jazyka C# 3.0, který ke zpracování obsahu pole používá standardní operátory dotazů:
using System;
using System.Linq;
using System.Collections.Generic;
class app {
static void Main() {
string[] names = { "Burke", "Connor", "Frank",
"Everett", "Albert", "George",
"Harris", "David" };
IEnumerable<string> query = from s in names
where s.Length == 5
orderby s
select s.ToUpper();
foreach (string item in query)
Console.WriteLine(item);
}
}
Pokud byste tento program zkompilovali a spustili, zobrazili byste tento výstup:
BURKE
DAVID
FRANK
To understand how language-integrated query works, we need to dissect the
first statement of our program.
IEnumerable<string> query = from s in names
where s.Length == 5
orderby s
select s.ToUpper();
Dotaz místní proměnné se inicializuje pomocí výrazu dotazu. Výraz dotazu pracuje s jedním nebo více zdroji informací použitím jednoho nebo více operátorů dotazu ze standardních operátorů dotazu nebo operátorů specifických pro doménu. Tento výraz používá tři standardní operátory dotazu: Where, OrderBy a Select.
Visual Basic 9.0 podporuje také LINQ. Tady je předchozí příkaz napsaný v jazyce Visual Basic 9.0:
Dim query As IEnumerable(Of String) = From s in names _
Where s.Length = 5 _
Order By s _
Select s.ToUpper()
Příkazy jazyka C# i Visual Basic, které jsou zde uvedené, používají výrazy dotazu. Podobně jako příkaz foreach jsou výrazy dotazů pohodlné deklarativní zkratky nad kódem, který byste mohli napsat ručně. Výše uvedené příkazy jsou sémanticky shodné s následující explicitní syntaxí zobrazenou v jazyce C#:
IEnumerable<string> query = names
.Where(s => s.Length == 5)
.OrderBy(s => s)
.Select(s => s.ToUpper());
Tato forma dotazu se nazývá dotaz založený na metodách . Argumenty operátorů Where, OrderBy a Select se nazývají výrazy lambda, které jsou fragmenty kódu podobně jako delegáty. Umožňují, aby standardní operátory dotazů byly definovány jednotlivě jako metody a prováděné dohromady pomocí zápisu s tečkou. Společně tyto metody tvoří základ rozšiřitelného dotazovacího jazyka.
Jazykové funkce podporující projekt LINQ
LINQ je zcela postaven na funkcích jazyka pro obecné účely, z nichž některé jsou v jazyce C# 3.0 a Visual Basic 9.0 novinkou. Každá z těchto funkcí má vlastní nástroj, ale souhrnně tyto funkce poskytují rozšiřitelný způsob definování dotazů a dotazovatelných rozhraní API. V této části prozkoumáme tyto jazykové funkce a zjistíme, jak přispívají k mnohem přímějšímu a deklarativnímu stylu dotazů.
Výrazy lambda a stromy výrazů
Mnoho operátorů dotazů umožňuje uživateli poskytnout funkci, která provádí filtrování, projekci nebo extrahování klíčů. Dotazovací zařízení vycházejí z konceptu výrazů lambda, které vývojářům poskytují pohodlný způsob psaní funkcí, které je možné předat jako argumenty pro následné vyhodnocení. Výrazy lambda jsou podobné delegátům CLR a musí dodržovat podpis metody definovaný typem delegáta. Pro ilustraci můžeme výše uvedený příkaz rozšířit do ekvivalentní, ale explicitnější formy pomocí typu delegáta Func :
Func<string, bool> filter = s => s.Length == 5;
Func<string, string> extract = s => s;
Func<string, string> project = s => s.ToUpper();
IEnumerable<string> query = names.Where(filter)
.OrderBy(extract)
.Select(project);
Výrazy lambda jsou přirozeným vývojem anonymních metod v jazyce C# 2.0. Předchozí příklad bychom například mohli napsat pomocí anonymních metod takto:
Func<string, bool> filter = delegate (string s) {
return s.Length == 5;
};
Func<string, string> extract = delegate (string s) {
return s;
};
Func<string, string> project = delegate (string s) {
return s.ToUpper();
};
IEnumerable<string> query = names.Where(filter)
.OrderBy(extract)
.Select(project);
Obecně platí, že vývojář může s operátory dotazů používat pojmenované metody, anonymní metody nebo výrazy lambda. Výrazy lambda mají výhodu v tom, že poskytují nejpřímější a nejkompaktnější syntaxi pro vytváření obsahu. Ještě důležitější je, že výrazy lambda je možné zkompilovat jako kód nebo data, což umožňuje zpracování výrazů lambda za běhu optimalizátory, překladateli a vyhodnocovači.
Obor názvů System.Linq.Expressions definuje rozlišující obecný typ , Výraz<T>, který označuje, že strom výrazu je žádoucí pro daný výraz lambda místo tradičního těla metody založené na IL. Stromy výrazů jsou efektivní reprezentací dat v paměti výrazů lambda a činí strukturu výrazu transparentní a explicitní.
Určení, zda kompilátor bude generovat spustitelný soubor IL nebo strom výrazu, je určeno způsobem použití výrazu lambda. Když je výraz lambda přiřazen proměnné, poli nebo parametru, jehož typ je delegát, kompilátor vygeneruje il, který je shodný s anonymní metodou. Když je výraz lambda přiřazen proměnné, poli nebo parametru, jehož typ je Výraz<T> pro některý typ delegáta T, kompilátor místo toho vygeneruje strom výrazu.
Představte si například následující dvě deklarace proměnných:
Func<int, bool> f = n => n < 5;
Expression<Func<int, bool>> e = n => n < 5;
Proměnná f je odkaz na delegáta, který je přímo spustitelný:
bool isSmall = f(2); // isSmall is now true
Proměnná e je odkaz na strom výrazů, který není přímo spustitelný:
bool isSmall = e(2); // compile error, expressions == data
Na rozdíl od delegátů, které jsou efektivně neprůshledný kód, můžeme se stromem výrazů pracovat stejně jako s jakoukoli jinou datovou strukturou v našem programu.
Expression<Func<int, bool>> filter = n => n < 5;
BinaryExpression body = (BinaryExpression)filter.Body;
ParameterExpression left = (ParameterExpression)body.Left;
ConstantExpression right = (ConstantExpression)body.Right;
Console.WriteLine("{0} {1} {2}",
left.Name, body.NodeType, right.Value);
Výše uvedený příklad rozloží strom výrazu za běhu a vytiskne následující řetězec:
n LessThan 5
Tato schopnost nakládat s výrazy jako s daty za běhu je důležitá pro vytvoření ekosystému knihoven třetích stran, které využívají abstrakce základních dotazů, které jsou součástí platformy. Implementace přístupu k datům LINQ to SQL využívá toto zařízení k překladu stromů výrazů na příkazy T-SQL vhodné pro vyhodnocení v úložišti.
Metody rozšíření
Výrazy lambda jsou jednou z důležitých součástí architektury dotazů. Rozšiřující metody jsou jiné. Rozšiřující metody kombinují flexibilitu "kachního psaní" oblíbenou v dynamických jazycích s ověřováním výkonu a kompilace statických jazyků. S rozšiřujícími metodami mohou třetí strany rozšířit veřejnou zakázku typu o nové metody a zároveň umožnit jednotlivým autorům typů poskytovat jejich vlastní specializovanou implementaci těchto metod.
Rozšiřující metody jsou ve statických třídách definovány jako statické metody, ale v metadatech CLR jsou označeny atributem [System.Runtime.CompilerServices.Extension]. Jazykům se doporučuje poskytovat přímou syntaxi rozšiřujících metod. V jazyce C# jsou rozšiřující metody označeny tímto modifikátorem, který se musí použít u prvního parametru rozšiřující metody. Podívejme se na definici nejjednoduššího operátoru dotazu Where:
namespace System.Linq {
using System;
using System.Collections.Generic;
public static class Enumerable {
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, bool> predicate) {
foreach (T item in source)
if (predicate(item))
yield return item;
}
}
}
Typ prvního parametru rozšiřující metody označuje, na jaký typ se rozšíření vztahuje. Ve výše uvedeném příkladu rozšiřující metoda Where rozšiřuje typ IEnumerable<T>. Vzhledem k tomu , že where je statická metoda, můžeme ji vyvolat přímo stejně jako jakoukoli jinou statickou metodu:
IEnumerable<string> query = Enumerable.Where(names,
s => s.Length < 6);
To, co ale dělá rozšiřující metody jedinečnými, je to, že se dají vyvolat také pomocí syntaxe instance:
IEnumerable<string> query = names.Where(s => s.Length < 6);
Metody rozšíření se řeší v době kompilace na základě toho, které metody rozšíření jsou v oboru. Při importu oboru názvů pomocí příkazu using v jazyce C# nebo příkazu Import v jazyce Visual Basic jsou do oboru přeneseny všechny metody rozšíření, které jsou definovány statickými třídami z tohoto oboru názvů.
Standardní operátory dotazů jsou definovány jako rozšiřující metody v typu System.Linq.Enumerable. Při zkoumání standardních operátorů dotazů si všimnete, že všechny kromě několika z nich jsou definovány v rámci rozhraní IEnumerable<T> . To znamená, že každý zdroj informací kompatibilní s IEnumerable<T> získá standardní operátory dotazů jednoduše přidáním následujícího příkazu using v jazyce C#:
using System.Linq; // makes query operators visible
Uživatelé, kteří chtějí nahradit standardní operátory dotazů pro konkrétní typ, můžou buď definovat vlastní metody se stejným názvem pro konkrétní typ s kompatibilními podpisy, nebo definovat nové rozšiřující metody se stejným názvem, které rozšiřují konkrétní typ. Uživatelé, kteří se chtějí vyhnout standardním operátorům dotazů úplně, jednoduše nemohou umístit System.Linq do oboru a psát vlastní metody rozšíření pro IEnumerable<T>.
Rozšiřující metody mají nejnižší prioritu z hlediska rozlišení a používají se pouze v případě, že neexistuje vhodná shoda s cílovým typem a jeho základními typy. To umožňuje uživatelsky definovaným typům poskytovat vlastní operátory dotazů, které mají přednost před standardními operátory. Představte si například následující vlastní kolekci:
public class MySequence : IEnumerable<int> {
public IEnumerator<int> GetEnumerator() {
for (int i = 1; i <= 10; i++)
yield return i;
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
public IEnumerable<int> Where(Func<int, bool> filter) {
for (int i = 1; i <= 10; i++)
if (filter(i))
yield return i;
}
}
Vzhledem k této definici třídy bude následující program používat implementaci MySequence.Where , nikoli rozšiřující metodu, protože metody instance mají přednost před rozšiřujícími metodami:
MySequence s = new MySequence();
foreach (int item in s.Where(n => n > 3))
Console.WriteLine(item);
Operátor OfType je jedním z mála standardních dotazovacích operátorů, které nerozšíří zdroj informací založený na IEnumerable<T>. Podívejme se na operátor dotazu OfType :
public static IEnumerable<T> OfType<T>(this IEnumerable source) {
foreach (object item in source)
if (item is T)
yield return (T)item;
}
OfType přijímá nejen IEnumerable<T-založené> zdroje, ale také zdroje, které jsou zapsány proti neparametrizované rozhraní IEnumerable , které bylo přítomno ve verzi 1.0 rozhraní .NET Framework. Operátor OfType umožňuje uživatelům použít standardní operátory dotazů na klasické kolekce .NET, jako je tento:
// "classic" cannot be used directly with query operators
IEnumerable classic = new OlderCollectionType();
// "modern" can be used directly with query operators
IEnumerable<object> modern = classic.OfType<object>();
V tomto příkladu proměnná poskytuje stejnou posloupnost hodnot jako klasická.modern
Jeho typ je však kompatibilní s moderním kódem IEnumerable<T> , včetně standardních operátorů dotazů.
Operátor OfType je také užitečný pro novější zdroje informací, protože umožňuje filtrování hodnot ze zdroje na základě typu. Při vytváření nové sekvence OfType jednoduše vynechá členy původní sekvence, které nejsou kompatibilní s argumentem typu. Představte si tento jednoduchý program, který extrahuje řetězce z heterogenního pole:
object[] vals = { 1, "Hello", true, "World", 9.1 };
IEnumerable<string> justStrings = vals.OfType<string>();
Když v příkazu foreach vytvoříme výčet proměnné justStrings, získáme posloupnost dvou řetězců: "Hello" a "World".
Deferred Query Evaluation
Pozorní čtenáři si mohli všimnout, že standardní operátor Where je implementován pomocí konstruktoru yield zavedeného v jazyce C# 2.0. Tato technika implementace je společná pro všechny standardní operátory, které vracejí sekvence hodnot. Použití výnosu má zajímavou výhodu, která spočívá v tom, že dotaz se ve skutečnosti nevyhodnocuje, dokud se iterace nepřekončí, a to buď pomocí příkazu foreach , nebo ručně pomocí podkladových metod GetEnumerator a MoveNext . Toto odložené vyhodnocení umožňuje uchovávat dotazy jako hodnoty založené na IEnumerable<T>, které mohou být vyhodnoceny vícekrát, přičemž pokaždé získáte potenciálně odlišné výsledky.
U mnoha aplikací se jedná přesně o požadované chování. Pro aplikace, které chtějí uložit výsledky vyhodnocení dotazu do mezipaměti, jsou k dispozici dva operátory , ToList a ToArray, které vynutí okamžité vyhodnocení dotazu a vrátí buď seznam<T> , nebo pole obsahující výsledky vyhodnocení dotazu.
Pokud chcete zjistit, jak funguje vyhodnocení odložených dotazů, zvažte tento program, který spustí jednoduchý dotaz přes pole:
// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };
// declare a variable that represents a query
IEnumerable<string> ayes = names.Where(s => s[0] == 'A');
// evaluate the query
foreach (string item in ayes)
Console.WriteLine(item);
// modify the original information source
names[0] = "Bob";
// evaluate the query again, this time no "Allen"
foreach (string item in ayes)
Console.WriteLine(item);
Dotaz se vyhodnocuje při každé iterace proměnné ayes . Pokud chcete označit, že je potřeba kopie výsledků uložená v mezipaměti, můžeme k dotazu jednoduše připojit operátor ToList nebo ToArray , a to takto:
// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };
// declare a variable that represents the result
// of an immediate query evaluation
string[] ayes = names.Where(s => s[0] == 'A').ToArray();
// iterate over the cached query results
foreach (string item in ayes)
Console.WriteLine(item);
// modifying the original source has no effect on ayes
names[0] = "Bob";
// iterate over result again, which still contains "Allen"
foreach (string item in ayes)
Console.WriteLine(item);
Funkce ToArray i ToList vynutí okamžité vyhodnocení dotazu. Totéž platí pro standardní operátory dotazů, které vracejí jednoúčelové hodnoty (například First, ElementAt, Sum, Average, All, Any).
Rozhraní IQueryable<T>
Stejný model odloženého spuštění je obvykle žádoucí pro zdroje dat, které implementují funkce dotazu pomocí stromů výrazů, jako je LINQ to SQL. Tyto zdroje dat mohou těžit z implementace rozhraní IQueryable<T> , pro které jsou všechny operátory dotazů vyžadované vzorem LINQ implementovány pomocí stromů výrazů. Každý IQueryable<T> má reprezentaci "kódu potřebného ke spuštění dotazu" ve formě stromu výrazů. Všechny operátory odloženého dotazu vrátí nový IQueryable<T> , který rozšíří strom výrazu o reprezentaci volání tohoto operátoru dotazu. Proto když nastane čas na vyhodnocení dotazu, obvykle proto, že je výčet IQueryable<T> , zdroj dat může zpracovat strom výrazů představující celý dotaz v jedné dávce. Například složitý LINQ to SQL dotaz získaný mnoha voláními operátorů dotazu může vést k tomu, že se do databáze odešle jenom jeden dotaz SQL.
Výhoda, kterou implementátoři zdrojů dat mají v tom, že tuto funkci odložení znovu použádí implementací rozhraní IQueryable<T>
, je zřejmá. Pro klienty, kteří píší dotazy, je na druhou stranu velkou výhodou mít pro vzdálené zdroje informací společný typ. Umožňuje jim nejen psát polymorfní dotazy, které je možné použít proti různým zdrojům dat, ale také otevírá možnost psaní dotazů, které jdou napříč doménami.
Inicializace složených hodnot
Výrazy lambda a rozšiřující metody nám poskytují vše, co potřebujeme pro dotazy, které jednoduše filtrují členy z posloupnosti hodnot. Většina výrazů dotazu také provádí projekce nad těmito členy a efektivně transformuje členy původní sekvence na členy, jejichž hodnota a typ se může lišit od původního. Pro podporu psaní těchto transformací, LINQ spoléhá na nový konstruktor s názvem inicializátory objektů k vytvoření nových instancí strukturovaných typů. Ve zbývající části tohoto dokumentu budeme předpokládat, že byl definován následující typ:
public class Person {
string name;
int age;
bool canCode;
public string Name {
get { return name; } set { name = value; }
}
public int Age {
get { return age; } set { age = value; }
}
public bool CanCode {
get { return canCode; } set { canCode = value; }
}
}
Inicializátory objektů nám umožňují snadno vytvářet hodnoty založené na veřejných polích a vlastnostech typu. Pokud například chcete vytvořit novou hodnotu typu Person, můžeme napsat tento příkaz:
Person value = new Person {
Name = "Chris Smith", Age = 31, CanCode = false
};
Sémanticky je tento příkaz ekvivalentní následující posloupnosti příkazů:
Person value = new Person();
value.Name = "Chris Smith";
value.Age = 31;
value.CanCode = false;
Inicializátory objektů jsou důležitou funkcí pro dotazy integrované do jazyka, protože umožňují vytváření nových strukturovaných hodnot v kontextech, kde jsou povoleny pouze výrazy (například ve výrazech lambda a stromech výrazů). Představte si například tento výraz dotazu, který pro každou hodnotu ve vstupní sekvenci vytvoří novou hodnotu Person :
IEnumerable<Person> query = names.Select(s => new Person {
Name = s, Age = 21, CanCode = s.Length == 5
});
Syntaxe inicializace objektu je také vhodná pro inicializaci polí strukturovaných hodnot. Představte si například tuto proměnnou pole, která je inicializována pomocí jednotlivých inicializátorů objektů:
static Person[] people = {
new Person { Name="Allen Frances", Age=11, CanCode=false },
new Person { Name="Burke Madison", Age=50, CanCode=true },
new Person { Name="Connor Morgan", Age=59, CanCode=false },
new Person { Name="David Charles", Age=33, CanCode=true },
new Person { Name="Everett Frank", Age=16, CanCode=true },
};
Strukturované hodnoty a typy
Projekt LINQ podporuje styl programování orientovaný na data, ve kterém některé typy existují především proto, aby poskytovaly statický "tvar" nad strukturovanou hodnotou místo plnohodnotného objektu se stavem i chováním. Když tuto premisu použijeme k logickému závěru, často se vývojáři starají o strukturu hodnoty a potřeba pojmenovaného typu pro tento tvar je málo k užitku. To vede k zavedení anonymních typů , které umožňují definovat nové struktury "vložené" s jejich inicializací.
V jazyce C# je syntaxe anonymních typů podobná syntaxi inicializace objektu s tím rozdílem, že je název typu vynechán. Představte si například následující dva příkazy:
object v1 = new Person {
Name = "Brian Smith", Age = 31, CanCode = false
};
object v2 = new { // note the omission of type name
Name = "Brian Smith", Age = 31, CanCode = false
};
Proměnné v1 a v2 ukazují na objekt v paměti, jehož typ CLR má tři veřejné vlastnosti Name, Age a CanCode. Proměnné se liší v tom, že verze 2 odkazuje na instanci anonymního typu. V CLR se anonymní typy nijak neliší od jakéhokoli jiného typu. Anonymní typy jsou speciální tím, že nemají ve vašem programovacím jazyce žádný smysluplný název. Jediným způsobem, jak vytvořit instance anonymního typu, je použití syntaxe uvedené výše.
Aby proměnné mohly odkazovat na instance anonymních typů, ale přesto stále těží ze statického psaní, zavádí jazyk C# implicitně typované místní proměnné: Místo názvu typu pro deklarace místních proměnných lze použít klíčové slovo var . Představte si například tento právní program C# 3.0:
var s = "Bob";
var n = 32;
var b = true;
Klíčové slovo var říká kompilátoru, aby odvozoval typ proměnné ze statického typu výrazu použitého k inicializaci proměnné. V tomto příkladu jsou typy s, n a břetězec, int a bool. Tento program je shodný s následujícím:
string s = "Bob";
int n = 32;
bool b = true;
Klíčové slovo var je pohodlné pro proměnné, jejichž typy mají smysluplné názvy, je však nezbytné pro proměnné, které odkazují na instance anonymních typů.
var value = new {
Name = " Brian Smith", Age = 31, CanCode = false
};
Ve výše uvedeném příkladu je hodnota proměnné anonymního typu, jehož definice je ekvivalentní následujícímu pseudo-C#:
internal class ??? {
string _Name;
int _Age;
bool _CanCode;
public string Name {
get { return _Name; } set { _Name = value; }
}
public int Age{
get { return _Age; } set { _Age = value; }
}
public bool CanCode {
get { return _CanCode; } set { _CanCode = value; }
}
public bool Equals(object obj) { ... }
public bool GetHashCode() { ... }
}
Anonymní typy nelze sdílet přes hranice sestavení; kompilátor však zajišťuje, že existuje maximálně jeden anonymní typ pro danou sekvenci dvojic název/typ vlastnosti v rámci každého sestavení.
Vzhledem k tomu, že anonymní typy se v projekcích často používají k výběru jednoho nebo více členů existující strukturované hodnoty, můžeme při inicializaci anonymního typu jednoduše odkazovat na pole nebo vlastnosti z jiné hodnoty. Výsledkem je, že nový anonymní typ získá vlastnost, jejíž název, typ a hodnota jsou zkopírovány z odkazované vlastnosti nebo pole.
Představte si například tento příklad, který vytvoří novou strukturovanou hodnotu kombinováním vlastností z jiných hodnot:
var bob = new Person { Name = "Bob", Age = 51, CanCode = true };
var jane = new { Age = 29, FirstName = "Jane" };
var couple = new {
Husband = new { bob.Name, bob.Age },
Wife = new { Name = jane.FirstName, jane.Age }
};
int ha = couple.Husband.Age; // ha == 51
string wn = couple.Wife.Name; // wn == "Jane"
Odkazování na pole nebo vlastnosti uvedené výše je jednoduše vhodnou syntaxí pro zápis následující explicitnější formy:
var couple = new {
Husband = new { Name = bob.Name, Age = bob.Age },
Wife = new { Name = jane.FirstName, Age = jane.Age }
};
V obou případech získá párová proměnná svoji vlastní kopii vlastností Name a Age z bob a jane.
Anonymní typy se nejčastěji používají v klauzuli select dotazu. Zvažte například následující dotaz:
var query = people.Select(p => new {
p.Name, BadCoder = p.Age == 11
});
foreach (var item in query)
Console.WriteLine("{0} is a {1} coder",
item.Name,
item.BadCoder ? "bad" : "good");
V tomto příkladu jsme dokázali vytvořit novou projekci typu Osoba , která přesně odpovídala obrazci potřebnému pro zpracování kódu, ale přesto nám poskytovala výhody statického typu.
Standardnější operátory dotazů
Kromě výše popsaných základních dotazovacích zařízení poskytuje řada operátorů užitečné způsoby manipulace se sekvencemi a vytváření dotazů, které uživateli poskytují vysokou míru kontroly nad výsledkem v rámci vhodných operátorů standardních dotazů.
Řazení a seskupování
Obecně platí, že výsledkem vyhodnocení dotazu je posloupnost hodnot, které jsou vytvořeny v určitém pořadí, které je vnitřní v podkladových zdrojích informací. Aby vývojáři měli explicitní kontrolu nad pořadím, ve kterém jsou tyto hodnoty vytvářeny, jsou pro řízení pořadí definovány standardní operátory dotazů. Nejzábavnější z těchto operátorů je operátor OrderBy .
Operátory OrderBy a OrderByDescending lze použít u libovolného zdroje informací a umožňují uživateli poskytnout funkci extrakce klíčů, která vytvoří hodnotu, která se použije k řazení výsledků. Funkce OrderBy a OrderByDescending také přijímají volitelnou porovnávací funkci, která se dá použít k uložení částečného pořadí u klíčů. Podívejme se na základní příklad:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
// unity sort
var s1 = names.OrderBy(s => s);
var s2 = names.OrderByDescending(s => s);
// sort by length
var s3 = names.OrderBy(s => s.Length);
var s4 = names.OrderByDescending(s => s.Length);
První dva výrazy dotazu vytvoří nové sekvence, které jsou založené na řazení členů zdroje na základě porovnání řetězců. Druhé dva dotazy vytvoří nové sekvence, které jsou založené na řazení členů zdroje na základě délky každého řetězce.
Chcete-li povolit více kritérií řazení, funkce OrderBy i OrderByDescending vrací hodnotu OrderedSequence<T> místo obecného IEnumerable<T>. Dva operátory jsou definovány pouze pro OrderedSequence<T>, a to ThenBy a ThenByDescending , které používají další (podřízené) kritérium řazení. Samotné thenBy/ThenByDescending vrátí OrderedSequence<T>, což umožňuje použít libovolný počet operátorů ThenBy/ThenByDescending :
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
var s1 = names.OrderBy(s => s.Length).ThenBy(s => s);
Vyhodnocení dotazu, na který v tomto příkladu odkazuje s1 , by přineslo následující posloupnost hodnot:
"Burke", "David", "Frank",
"Albert", "Connor", "George", "Harris",
"Everett"
Kromě řady operátorů OrderBy zahrnují standardní operátory dotazů také operátor Reverse . Funkce Reverse jednoduše vyčíslí sekvenci a vrátí stejné hodnoty v obráceném pořadí. Na rozdíl od OrderBynezohlední reverse při určování pořadí samotné skutečné hodnoty, ale spoléhá výhradně na pořadí, v jakém jsou hodnoty vytvářeny podkladovým zdrojem.
Operátor OrderBy vynucuje pořadí řazení pro sekvenci hodnot. Standardní operátory dotazů také zahrnují operátor GroupBy , který ukládá dělení na sekvenci hodnot na základě funkce extrakce klíčů. Operátor GroupBy vrátí sekvenci hodnot IGrouping , jednu pro každou zjištěnou jedinečnou hodnotu klíče. IGrouping je IEnumerable, který navíc obsahuje klíč, který byl použit k extrahování jeho obsahu:
public interface IGrouping<K, T> : IEnumerable<T> {
public K Key { get; }
}
Nejjednodušší použití GroupBy vypadá takto:
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
// group by length
var groups = names.GroupBy(s => s.Length);
foreach (IGrouping<int, string> group in groups) {
Console.WriteLine("Strings of length {0}", group.Key);
foreach (string value in group)
Console.WriteLine(" {0}", value);
}
Při spuštění tento program vypíše následující:
Strings of length 6
Albert
Connor
George
Harris
Strings of length 5
Burke
David
Frank
Strings of length 7
Everett
A la Select, GroupBy umožňuje poskytnout funkci projekce, která se používá k naplnění členů skupin.
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
// group by length
var groups = names.GroupBy(s => s.Length, s => s[0]);
foreach (IGrouping<int, char> group in groups) {
Console.WriteLine("Strings of length {0}", group.Key);
foreach (char value in group)
Console.WriteLine(" {0}", value);
}
Tato varianta zobrazí následující:
Strings of length 6
A
C
G
H
Strings of length 5
B
D
F
Strings of length 7
E
Poznámka Z tohoto příkladu, že projektovaný typ nemusí být stejný jako zdroj. V tomto případě jsme vytvořili seskupení celých čísel na znaky z posloupnosti řetězců.
Operátory agregace
Pro agregaci posloupnosti hodnot do jedné hodnoty je definováno několik standardních operátorů dotazu. Nejobecnějším operátorem agregace je Agregace, která je definována takto:
public static U Aggregate<T, U>(this IEnumerable<T> source,
U seed, Func<U, T, U> func) {
U result = seed;
foreach (T element in source)
result = func(result, element);
return result;
}
Operátor Aggregate (Agregace ) usnadňuje provádění výpočtu pro posloupnost hodnot. Agregace funguje tak, že zavolá výraz lambda jednou pro každého člena podkladové sekvence. Pokaždé, když Agregace volá výraz lambda, předá člen ze sekvence i agregovanou hodnotu (počáteční hodnota je počáteční parametr agregace). Výsledek výrazu lambda nahradí předchozí agregovanou hodnotu a Funkce Aggregate vrátí konečný výsledek výrazu lambda.
Tento program například používá agregaci k kumulaci celkového počtu znaků v poli řetězců:
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
int count = names.Aggregate(0, (c, s) => c + s.Length);
// count == 46
Kromě operátoru Agregace pro obecné účely zahrnují standardní operátory dotazů také operátor počtu pro obecné účely a čtyři operátory číselné agregace (Min, Max, Součet a Průměr), které tyto běžné agregační operace zjednodušují. Funkce číselné agregace pracují ve sekvencích číselných typů (například int, double, decimal) nebo nad sekvencemi libovolných hodnot, pokud je k dispozici funkce, která promítá členy posloupnosti do číselného typu.
Tento program znázorňuje obě právě popsané formy operátoru Sum :
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
int total1 = numbers.Sum(); // total1 == 55
int total2 = names.Sum(s => s.Length); // total2 == 46
Poznámka Druhý příkaz Sum je ekvivalentní předchozímu příkladu pomocí agregace.
Vybrat vs. SelectMany
Operátor Select vyžaduje, aby transformační funkce vygenerovala jednu hodnotu pro každou hodnotu ve zdrojové sekvenci. Pokud transformační funkce vrátí hodnotu, která je sama o sobě posloupností, je na příjemci, aby dílčí sekvence přecházely ručně. Představte si například tento program, který rozděluje řetězce na tokeny pomocí existující metody String.Split :
string[] text = { "Albert was here",
"Burke slept late",
"Connor is happy" };
var tokens = text.Select(s => s.Split(' '));
foreach (string[] line in tokens)
foreach (string token in line)
Console.Write("{0}.", token);
Při spuštění tento program vytiskne následující text:
Albert.was.here.Burke.slept.late.Connor.is.happy.
V ideálním případě bychom chtěli, aby dotaz vrátil sloučenou sekvenci tokenů a nevystavil zprostředkující řetězec[] příjemci. K tomu použijeme operátor SelectMany místo operátoru Select . Operátor SelectMany funguje podobně jako operátor Select . Liší se v tom, že se očekává, že transformační funkce vrátí sekvenci, která je pak rozšířena operátorem SelectMany . Tady je náš program přepsaný pomocí SelectMany:
string[] text = { "Albert was here",
"Burke slept late",
"Connor is happy" };
var tokens = text.SelectMany(s => s.Split(' '));
foreach (string token in tokens)
Console.Write("{0}.", token);
Použití funkce SelectMany způsobí, že se každá mezisekvence rozšíří jako součást normálního vyhodnocení.
SelectMany je ideální pro kombinování dvou zdrojů informací:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
var query = names.SelectMany(n =>
people.Where(p => n.Equals(p.Name))
);
Ve výrazu lambda předaného do SelectMany se vnořený dotaz vztahuje na jiný zdroj, ale má v oboru
n
parametr předaný z vnějšího zdroje. Tedy lidi. Kde je volána jednou pro každé n, s výslednými sekvencemi zploštěnými funkcí SelectMany pro konečný výstup. Výsledkem je posloupnost všech lidí, jejichž jméno se zobrazí v poli názvů .
Operátory spojení
V objektově orientovaném programu budou objekty, které jsou vzájemně spojené, obvykle propojeny s odkazy na objekty, které lze snadno procházet. Totéž obvykle neplatí pro externí zdroje informací, kde datové položky často nemají jinou možnost, než na sebe symbolicky "odkazovat" s ID nebo jinými daty, které mohou jedinečně identifikovat entitu, na kterou odkazuje. Koncept spojení označuje operaci připojování prvků sekvence společně s prvky, se kterými "odpovídají" z jiné sekvence.
Předchozí příklad s SelectMany ve skutečnosti dělá přesně to, když porovnává řetězce s lidmi, jejichž jména jsou tyto řetězce. Pro tento konkrétní účel ale není přístup SelectMany příliš efektivní – bude procházet všechny prvky lidí pro každý prvek jména. Spojením všech informací o tomto scénáři ( dva zdroje informací a "klíče", podle kterých jsou spárovány) do jednoho volání metody je operátor Join schopen udělat mnohem lepší práci:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
var query = names.Join(people, n => n, p => p.Name, (n,p) => p);
To je trochu huba, ale podívejte se, jak do sebe jednotlivé části zapadají: Metoda Join se nazývá na "vnějším" zdroji dat , názvy. Prvním argumentem je "vnitřní" zdroj dat, lidé. Druhý a třetí argument jsou výrazy lambda, které extrahují klíče z prvků vnějšího a vnitřního zdroje. Tyto klíče jsou to, co join metoda používá ke shodě prvků. Tady chceme, aby se samotná jména shodovaly s vlastností Name (Jméno ) lidí. Konečný výraz lambda je pak zodpovědný za vytvoření prvků výsledné sekvence: Volá se s každou dvojicí shodných prvků n a p a používá se k tvarování výsledku. V tomto případě se rozhodneme zahodit n a vrátit p. Konečným výsledkem je seznam prvků osob, jejichžjméno je v seznamu jmen.
Silnějším bratrancem join je operátor GroupJoin . GroupJoin se liší od funkce Join v tom, jak se používá výraz lambda tvarující výsledek: Místo vyvolání s jednotlivými dvojicemi vnějších a vnitřních prvků bude volán pouze jednou pro každý vnější prvek s posloupností všech vnitřních elementů, které odpovídají danému vnějšímu elementu. Aby to bylo betonové:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
var query = names.GroupJoin(people, n => n, p => p.Name,
(n, matching) =>
new { Name = n, Count = matching.Count() }
);
Toto volání vytvoří sekvenci jmen, se kterými jste začali, společně s počtem lidí, kteří toto jméno mají. Operátor GroupJoin tedy umožňuje založit výsledky na celé "sadě shod" pro vnější prvek.
Syntaxe dotazů
Existující příkaz foreach v jazyce C# poskytuje deklarativní syntaxi pro iteraci přes metody IEnumerable/IEnumerator rozhraní .NET Frameworks. Příkaz foreach je přísně volitelný, ale ukázal se jako velmi pohodlný a oblíbený jazykový mechanismus.
Na základě tohoto precedentu výrazy dotazů zjednodušují dotazy s deklarativní syntaxí pro nejběžnější operátory dotazů: Where, Join, GroupJoin, Select, SelectMany, GroupBy, OrderBy, ThenBy, OrderByDescending, ThenByDescending a Cast.
Začněme jednoduchým dotazem, kterým jsme začali tento dokument:
IEnumerable<string> query = names
.Where(s => s.Length == 5)
.OrderBy(s => s)
.Select(s => s.ToUpper());
Pomocí výrazu dotazu můžeme přepsat tento přesný příkaz takto:
IEnumerable<string> query = from s in names
where s.Length == 5
orderby s
select s.ToUpper();
Podobně jako příkaz foreach v jazyce C# jsou výrazy dotazu kompaktnější a čitelnější, ale jsou zcela volitelné. Každý výraz, který lze zapsat jako výraz dotazu, má odpovídající (i když podrobnější) syntaxi s použitím tečkové notace.
Začněme tím, že se podíváme na základní strukturu výrazu dotazu. Každý syntaktický výraz dotazu v jazyce C# začíná klauzulí from a končí klauzulí select nebo group . Za počáteční klauzulí from může následovat nula nebo více klauzulí from, let, where, join a orderby . Každá klauzule from je generátor, který zavádí proměnnou rozsahu v sekvenci; každá klauzule let pojmenuje výsledek výrazu; a klauzule each where je filtr, který vylučuje položky z výsledku. Každá klauzule join koreluje nový zdroj dat s výsledky předchozích klauzulí. Klauzule orderby určuje pořadí výsledku:
query-expression ::= from-clause query-body
query-body ::=
query-body-clause* final-query-clause query-continuation?
query-body-clause ::=
(from-clause
| join-clause
| let-clause
| where-clause
| orderby-clause)
from-clause ::=from itemName in srcExpr
join-clause ::=join itemName in srcExpr on keyExpr equals keyExpr
(into itemName)?
let-clause ::=let itemName = selExpr
where-clause ::= where predExpr
orderby-clause ::= orderby (keyExpr (ascending | descending)?)*
final-query-clause ::=
(select-clause | groupby-clause)
select-clause ::= select selExpr
groupby-clause ::= group selExpr by keyExprquery-continuation ::= intoitemName query-body
Představte si například tyto dva výrazy dotazu:
var query1 = from p in people
where p.Age > 20
orderby p.Age descending, p.Name
select new {
p.Name, Senior = p.Age > 30, p.CanCode
};
var query2 = from p in people
where p.Age > 20
orderby p.Age descending, p.Name
group new {
p.Name, Senior = p.Age > 30, p.CanCode
} by p.CanCode;
Kompilátor zachází s těmito výrazy dotazu, jako by byly napsány pomocí následující explicitní tečkové notace:
var query1 = people.Where(p => p.Age > 20)
.OrderByDescending(p => p.Age)
.ThenBy(p => p.Name)
.Select(p => new {
p.Name,
Senior = p.Age > 30,
p.CanCode
});
var query2 = people.Where(p => p.Age > 20)
.OrderByDescending(p => p.Age)
.ThenBy(p => p.Name)
.GroupBy(p => p.CanCode,
p => new {
p.Name,
Senior = p.Age > 30,
p.CanCode
});
Výrazy dotazu procházejí mechanickým překladem na volání metod se specifickými názvy. Přesná implementace operátoru dotazu, která je zvolena, proto závisí jak na typu dotazovaných proměnných, tak na metodách rozšíření, které jsou v oboru.
Dosud zobrazené výrazy dotazu používaly pouze jeden generátor. Při použití více generátorů se každý další generátor vyhodnotí v kontextu svého předchůdce. Podívejte se například na tuto drobnou změnu našeho dotazu:
var query = from s1 in names
where s1.Length == 5
from s2 in names
where s1 == s2
select s1 + " " + s2;
Při spuštění pro toto vstupní pole:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
získáme následující výsledky:
Burke Burke
Frank Frank
David David
Výše uvedený výraz dotazu se rozbalí na tento výraz dot-notation:
var query = names.Where(s1 => s1.Length == 5)
.SelectMany(s1 => names, (s1,s2) => new {s1,s2})
.Where($1 => $1.s1 == $1.s2)
.Select($1 => $1.s1 + " " + $1.s2);
Poznámka Tato verze SelectMany přebírá výraz lambda navíc, který se používá k vytvoření výsledku na základě prvků z vnější a vnitřní sekvence. V tomto výrazu lambda se obě proměnné rozsahu shromažďují v anonymním typu. Kompilátor vymyslí název proměnné $1 , který tento anonymní typ označí v následných výrazech lambda.
Speciálním druhem generátoru je klauzule join , která zavede prvky jiného zdroje, které odpovídají prvkům předchozích klauzulí podle daných klíčů. Klauzule join může vydělovat shodné prvky jeden po druhém, ale pokud je zadána klauzulí into , odpovídající prvky se přidělí jako skupina:
var query = from n in names
join p in people on n equals p.Name into matching
select new { Name = n, Count = matching.Count() };
Není divu, že se tento dotaz rozšiřuje přímo na dotaz, který jsme viděli dříve:
var query = names.GroupJoin(people, n => n, p => p.Name,
(n, matching) =>
new { Name = n, Count = matching.Count() }
);
Často je užitečné považovat výsledky jednoho dotazu za generátor v následujícím dotazu. Aby to bylo možné, používají výrazy dotazu klíčové slovo into k rozdělení nového výrazu dotazu za klauzuli select nebo group. Tomu se říká pokračování dotazu.
Klíčové slovo into je zvláště užitečné pro následné zpracování výsledků klauzule seskupování
podle . Představte si například tento program:
var query = from item in names
orderby item
group item by item.Length into lengthGroups
orderby lengthGroups.Key descending
select lengthGroups;
foreach (var group in query) {
Console.WriteLine("Strings of length {0}", group.Key);
foreach (var val in group)
Console.WriteLine(" {0}", val);
}
Tento program vypíše následující výstupy:
Strings of length 7
Everett
Strings of length 6
Albert
Connor
George
Harris
Strings of length 5
Burke
David
Frank
V této části jsme popsali, jak C# implementuje výrazy dotazů. Jiné jazyky se můžou rozhodnout, že budou podporovat další operátory dotazů s explicitní syntaxí, nebo nebudou mít výrazy dotazu vůbec.
Je důležité si uvědomit, že syntaxe dotazu není v žádném případě pevně připojena ke standardním operátorům dotazů. Jedná se o čistě syntaktickou funkci, která se vztahuje na cokoli, co splňuje vzor dotazu implementací základních metod s příslušnými názvy a podpisy. Standardní operátory dotazů popsané výše to dělají pomocí rozšiřujících metod k rozšíření rozhraní IEnumerable<T> . Vývojáři můžou využít syntaxi dotazu u libovolného typu, pokud se ujistí, že dodržuje vzor dotazu, a to buď přímou implementací potřebných metod, nebo jejich přidáním jako rozšiřujících metod.
Tato rozšiřitelnost je využívána v samotném projektu LINQ zřízením dvou rozhraní API s podporou LINQ, konkrétně LINQ to SQL, která implementuje model LINQ pro přístup k datům založeným na SQL, a LINQ to XML, která umožňuje dotazy LINQ na data XML. Obě jsou popsány v následujících částech.
LINQ to SQL: Integrace SQL
.NET Language-Integrated Query lze použít k dotazování relačních úložišť dat bez opuštění prostředí syntaxe nebo kompilace místního programovacího jazyka. Toto zařízení s kódovým názvem LINQ to SQL využívá integraci informací o schématu SQL do metadat CLR. Tato integrace kompiluje tabulky SQL a definice zobrazení do typů CLR, ke kterým je možné přistupovat z libovolného jazyka.
LINQ to SQL definuje dva základní atributy [ Table] a [Column], které označují, které typy a vlastnosti CLR odpovídají externím datům SQL. Atribut [Table] lze použít na třídu a přidružit typ CLR k pojmenované tabulce nebo zobrazení SQL. Atribut [Column] lze použít pro jakékoli pole nebo vlastnost a přidruží člena k pojmenovaným sloupcům SQL. Oba atributy jsou parametrizovány, aby bylo možné zachovat metadata specifická pro SQL. Představte si například tuto jednoduchou definici schématu SQL:
create table People (
Name nvarchar(32) primary key not null,
Age int not null,
CanCode bit not null
)
create table Orders (
OrderID nvarchar(32) primary key not null,
Customer nvarchar(32) not null,
Amount int
)
Ekvivalent CLR vypadá takto:
[Table(Name="People")]
public class Person {
[Column(DbType="nvarchar(32) not null", Id=true)]
public string Name;
[Column]
public int Age;
[Column]
public bool CanCode;
}
[Table(Name="Orders")]
public class Order {
[Column(DbType="nvarchar(32) not null", Id=true)]
public string OrderID;
[Column(DbType="nvarchar(32) not null")]
public string Customer;
[Column]
public int? Amount;
}
Poznámka Tento příklad mapování sloupců s možnou hodnotou null na typy v CLR (typy s možnou hodnotou null se poprvé objevily ve verzi 2.0 rozhraní .NET Framework) a že pro typy SQL, které nemají shodu 1:1 s typem CLR (například nvarchar, char, text), je původní typ SQL zachován v metadatech CLR.
Pokud chcete vydat dotaz na relační úložiště, LINQ to SQL implementace vzoru LINQ přeloží dotaz z jeho formuláře stromu výrazů do výrazu SQL a ADO.NET DbCommand objekt vhodný pro vzdálené vyhodnocení. Představte si například tento jednoduchý dotaz:
// establish a query context over ADO.NET sql connection
DataContext context = new DataContext(
"Initial Catalog=petdb;Integrated Security=sspi");
// grab variables that represent the remote tables that
// correspond to the Person and Order CLR types
Table<Person> custs = context.GetTable<Person>();
Table<Order> orders = context.GetTable<Order>();
// build the query
var query = from c in custs
from o in orders
where o.Customer == c.Name
select new {
c.Name,
o.OrderID,
o.Amount,
c.Age
};
// execute the query
foreach (var item in query)
Console.WriteLine("{0} {1} {2} {3}",
item.Name, item.OrderID,
item.Amount, item.Age);
Typ DataContext poskytuje jednoduchý překladač, který překládá standardní operátory dotazů na SQL. DataContext používá existující ADO.NET IDbConnection pro přístup k úložišti a lze ho inicializovat buď vytvořeným objektem připojení ADO.NET, nebo připojovacím řetězcem, který lze použít k jeho vytvoření.
GetTable Metoda poskytuje IEnumerable-kompatibilní proměnné, které lze použít ve výrazech dotazu k reprezentaci vzdálené tabulky nebo zobrazení. Volání GetTable nezpůsobují žádnou interakci s databází, ale představují potenciál interakce se vzdálenou tabulkou nebo zobrazením pomocí výrazů dotazu. V našem příkladu výše se dotaz nepřenese do úložiště, dokud program ne iteruje výraz dotazu, v tomto případě pomocí příkazu foreach v jazyce C#. Když program poprvé iteruje dotaz, stroj DataContext přeloží strom výrazu do následujícího příkazu SQL, který se odešle do úložiště:
SELECT [t0].[Age], [t1].[Amount],
[t0].[Name], [t1].[OrderID]
FROM [Customers] AS [t0], [Orders] AS [t1]
WHERE [t1].[Customer] = [t0].[Name]
Je důležité si uvědomit, že když se funkce dotazování zabudují přímo do místního programovacího jazyka, získají vývojáři plný výkon relačního modelu, aniž by museli staticky upéct relace do typu CLR. Komplexní mapování objektů a relačních objektů může tuto základní funkci dotazování využít také pro uživatele, kteří tuto funkci chtějí. LINQ to SQL poskytuje funkci objektově-relačního mapování, pomocí které může vývojář definovat a procházet vztahy mezi objekty. Orders můžete odkazovat jako vlastnost třídy Customer pomocí mapování, takže nepotřebujete explicitní spojení k jejich spojení dohromady. Externí soubory mapování umožňují oddělit mapování od objektového modelu a získat tak bohatší možnosti mapování.
LINQ to XML: Integrace XML
.NET Language-Integrated Query for XML (LINQ to XML) umožňuje dotazování dat XML pomocí standardních operátorů dotazu a operátorů specifických pro strom, které poskytují navigaci jako XPath mezi potomky, předky a na stejné úrovni. Poskytuje efektivní reprezentaci xml v paměti, která se integruje s existující infrastrukturou System.Xml čtečky/zapisovače a snadněji se používá než W3C DOM. Existují tři typy, které dělají většinu práce při integraci XML s dotazy: XName, XElement a XAttribute.
XName poskytuje snadno použitelný způsob, jak pracovat s identifikátory kvalifikovanými pro obor názvů (QNames) používanými jako názvy elementů i atributů. XName zpracovává efektivní atomizaci identifikátorů transparentně a umožňuje použití symbolů nebo prostých řetězců všude, kde je QName potřeba.
Elementy a atributy XML jsou reprezentovány pomocí XElement a XAttribute v uvedeném pořadí. XElement a XAttribute podporují normální syntaxi konstrukce, což vývojářům umožňuje psát výrazy XML pomocí přirozené syntaxe:
var e = new XElement("Person",
new XAttribute("CanCode", true),
new XElement("Name", "Loren David"),
new XElement("Age", 31));
var s = e.ToString();
To odpovídá následujícímu kódu XML:
<Person CanCode="true">
<Name>Loren David</Name>
<Age>31</Age>
</Person>
Všimněte si, že k vytvoření výrazu XML není potřeba žádný vzor továrny založený na modelu DOM a že implementace ToString získala textový KÓD XML. Elementy XML lze také vytvořit z existující XmlReader nebo z řetězcového literálu:
var e2 = XElement.Load(xmlReader);
var e1 = XElement.Parse(
@"<Person CanCode='true'>
<Name>Loren David</Name>
<Age>31</Age>
</Person>");
XElement také podporuje generování XML pomocí existující typ XmlWriter .
XElement spolupracuje s operátory dotazů, což vývojářům umožňuje psát dotazy na jiné informace než XML a vytvářet výsledky XML vytvořením XElements v těle klauzule select:
var query = from p in people
where p.CanCode
select new XElement("Person",
new XAttribute("Age", p.Age),
p.Name);
Tento dotaz vrátí sekvenci XElements. Aby bylo možné sestavit XElements z výsledku tohoto typu dotazu, umožňuje konstruktor XElement sekvence prvků, které mají být předány jako argumenty přímo:
var x = new XElement("People",
from p in people
where p.CanCode
select
new XElement("Person",
new XAttribute("Age", p.Age),
p.Name));
Výsledkem tohoto výrazu XML je následující kód XML:
<People>
<Person Age="11">Allen Frances</Person>
<Person Age="59">Connor Morgan</Person>
</People>
Výše uvedený příkaz má přímý překlad do jazyka Visual Basic. Jazyk Visual Basic 9.0 však podporuje také použití literálů XML, které umožňují výrazy dotazu vyjádřit pomocí deklarativní syntaxe XML přímo z jazyka Visual Basic. Předchozí příklad by mohl být vytvořen pomocí příkazu jazyka Visual Basic:
Dim x = _
<People>
<%= From p In people __
Where p.CanCode _
Select <Person Age=<%= p.Age %>>p.Name</Person> _
%>
</People>
Příklady dosud ukázaly, jak vytvořit nové hodnoty XML pomocí dotazu integrovaného do jazyka. XElement a XAttribute typy také zjednodušují extrakci informací ze struktur XML. XElement poskytuje přístupové metody, které umožňují použití výrazů dotazu na tradiční osy XPath. Například následující dotaz extrahuje pouze jména z XElementu výše:
IEnumerable<string> justNames =
from e in x.Descendants("Person")
select e.Value;
//justNames = ["Allen Frances", "Connor Morgan"]
K extrakci strukturovaných hodnot z XML jednoduše použijeme výraz inicializátoru objektů v klauzuli select:
IEnumerable<Person> persons =
from e in x.Descendants("Person")
select new Person {
Name = e.Value,
Age = (int)e.Attribute("Age")
};
Všimněte si, že XAttribute a XElement podporují explicitní převody extrahovat textovou hodnotu jako primitivní typ. Pokud chcete vyřešit chybějící data, můžeme jednoduše přetypovat na typ s možnou hodnotou null:
IEnumerable<Person> persons =
from e in x.Descendants("Person")
select new Person {
Name = e.Value,
Age = (int?)e.Attribute("Age") ?? 21
};
V tomto případě použijeme výchozí hodnotu 21 , když chybí atribut Age .
Visual Basic 9.0 poskytuje podporu přímého jazyka pro elementy, atribut a potomky přístup metody XElement, což umožňuje přístup k datům založeným na XML pomocí kompaktnější a přímé syntaxe označované vlastnosti osy XML. Pomocí této funkce můžeme napsat předchozí příkaz jazyka C# takto:
Dim persons = _
From e In x...<Person> _
Select new Person { _
.Name = e.Value, _
.Age = IIF(e.@Age, 21) _
}
V jazyce Visual Basic x...<Osoba> získá všechny položky v kolekci Potomci x s názvem Person, zatímco výraz e.@Age najde všechny XAttributes s názvem Age.
Vlastnost Value získá první atribut v kolekci a zavolá vlastnost Value pro tento atribut.
Souhrn
.NET Language-Integrated Query přidá do modulu CLR a jazyků, které na něj cílí, možnosti dotazů. Dotazovací zařízení staví na výrazech lambda a stromech výrazů, aby bylo možné použít predikáty, projekce a výrazy pro extrakci klíčů jako neprůhledný spustitelný kód nebo jako transparentní data v paměti vhodná pro následné zpracování nebo překlad. Standardní operátory dotazů definované projektem LINQ pracují s libovolným zdrojem informací založeným na jazyce IEnumerable<T> a jsou integrovány s ADO.NET (LINQ to SQL) a System.Xml (LINQ to XML), aby relační data a data XML získala výhody dotazů integrovaných v jazyce.
Standardní operátory dotazů v kostce
Operátor | Popis |
---|---|
Kde | Operátor omezení založený na funkci predikátu |
Select/SelectMany | Operátory projekce založené na selektorové funkci |
Take/Skip/TakeWhile/SkipWhile | Dělení operátorů na základě funkce pozice nebo predikátu |
Join/GroupJoin | Operátory spojení založené na funkcích selektoru klíčů |
Concat | Operátor zřetězení |
OrderBy/ThenBy/OrderByDescending/ThenByDescending | Operátory řazení ve vzestupném nebo sestupném pořadí na základě volitelných funkcí pro výběr kláves a porovnávače |
Reverse | Operátor řazení, který vrací pořadí sekvence |
GroupBy | Operátor seskupení na základě volitelných funkcí selektoru a porovnávače klíčů |
Distinct | Operátor set odebere duplicity. |
Sjednocení/Protínají se | Operátory sady vracející sjednocení sady nebo průnik |
S výjimkou | Operátor set vracející rozdíl sady |
AsEnumerable | Převodní operátor na IEnumerable<T> |
ToArray/ToList | Operátor převodu na pole nebo seznam<T> |
ToDictionary/ToLookup | Převodní operátory do slovníku<K,T> nebo vyhledávání<K,T> (více slovníků) na základě funkce voliče klíčů |
Typ/přetypování | Převodní operátory na IEnumerable<T> založené na filtrování podle nebo převodu na argument typu |
Sequenceequal | Operátor rovnosti, který kontroluje rovnost párového prvku |
First/FirstOrDefault/Last/LastOrDefault/Single/SingleOrDefault | Operátory elementů vracející počáteční/konečný/pouze element založený na volitelné funkci predikátu |
ElementAt/ElementAtOrDefault | Operátory elementu vracející element na základě pozice |
Defaultifempty | Operátor elementu nahrazující prázdnou sekvenci jednoúčelovou sekvencí s výchozí hodnotou |
Rozsah | Operátor generování vracející čísla v oblasti |
Repeat | Operátor generování vracející více výskytů dané hodnoty |
Prázdné | Operátor generování vracející prázdnou sekvenci |
Any/All | Kontrola kvantifikátoru pro existenční nebo univerzální spokojenost predikátové funkce |
Contains | Kontrola přítomnosti daného prvku kvantifikátorem |
Count/LongCount | Agregace operátorů počítání prvků na základě volitelné predikátové funkce |
Sum/Min/Max/Average | Agregované operátory založené na volitelných selektorových funkcích |
Agregace | Agregační operátor, který shromažďuje více hodnot na základě akumulační funkce a volitelného počátečního |