Úvod do dotazů LINQ v jazyce C#

Dotaz je výraz, který načítá data ze zdroje dat. Různé zdroje dat mají různé nativní dotazovací jazyky, například SQL pro relační databáze a XQuery pro XML. Vývojáři se musí naučit nový dotazovací jazyk pro každý typ zdroje dat nebo formátu dat, který musí podporovat. LINQ tuto situaci zjednodušuje tím, že nabízí konzistentní jazykový model jazyka C# pro typy zdrojů a formátů dat. V dotazu LINQ vždy pracujete s objekty jazyka C#. Stejné základní vzory kódování použijete k dotazování a transformaci dat v dokumentech XML, databázích SQL, kolekcích .NET a libovolném jiném formátu, pokud je k dispozici poskytovatel LINQ.

Tři části operace dotazu

Všechny operace dotazu LINQ se skládají ze tří různých akcí:

  1. Získejte zdroj dat.
  2. Vytvořte dotaz.
  3. Spusťte dotaz.

Následující příklad ukazuje, jak jsou tři části operace dotazu vyjádřeny ve zdrojovém kódu. Příklad používá celočíselné pole jako zdroj dat pro usnadnění; stejné koncepty se však vztahují i na jiné zdroje dat. Tento příklad se odkazuje ve zbytku tohoto článku.

// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

// 3. Query execution.
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

Následující obrázek znázorňuje úplnou operaci dotazu. V LINQ se provádění dotazu liší od samotného dotazu. Jinými slovy, nenačítáte žádná data vytvořením proměnné dotazu.

Diagram úplné operace dotazu LINQ

Zdroj dat

Zdroj dat v předchozím příkladu je pole, které podporuje obecné IEnumerable<T> rozhraní. To znamená, že se dá dotazovat pomocí LINQ. Dotaz se provádí v foreach příkazu a foreach vyžaduje IEnumerable nebo IEnumerable<T>. Typy, které podporují IEnumerable<T> nebo odvozené rozhraní, jako je obecný, IQueryable<T> se nazývají typy s možností dotazování.

Dotazovatelný typ nevyžaduje žádné úpravy ani zvláštní zacházení, které by sloužily jako zdroj dat LINQ. Pokud zdrojová data ještě nejsou v paměti jako dotazovatelný typ, musí ho zprostředkovatel LINQ reprezentovat jako takový. Například LINQ to XML načte dokument XML do dotazovatelného XElement typu:

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

Pomocí EntityFramework vytvoříte objektově-relační mapování mezi třídami jazyka C# a schématem databáze. Zapisujete dotazy na objekty a v době běhu entityFramework zpracovává komunikaci s databází. V následujícím příkladu představuje Customers konkrétní tabulku v databázi a typ výsledku dotazu , IQueryable<T>odvozen od IEnumerable<T>.

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

Další informace o tom, jak vytvořit konkrétní typy zdrojů dat, najdete v dokumentaci pro různé poskytovatele LINQ. Základní pravidlo je však jednoduché: zdroj dat LINQ je jakýkoli objekt, který podporuje obecné IEnumerable<T> rozhraní, nebo rozhraní, které z něj dědí, obvykle IQueryable<T>.

Poznámka:

Jako zdroj dat LINQ lze použít i typy, jako ArrayList je například podpora ne generického IEnumerable rozhraní. Další informace naleznete v tématu Jak dotazovat ArrayList pomocí LINQ (C#).

Dotaz

Dotaz určuje, jaké informace se mají načíst ze zdroje nebo zdrojů dat. Volitelně také dotaz určuje, jak se mají tyto informace před vrácením řadit, seskupit a tvarovat. Dotaz je uložen v proměnné dotazu a inicializován pomocí výrazu dotazu. K zápisu dotazů použijete syntaxi dotazu jazyka C#.

Dotaz v předchozím příkladu vrátí všechna sudá čísla z celočíselného pole. Výraz dotazu obsahuje tři klauzule: from, wherea select. (Pokud znáte SQL, všimli jste si, že řazení klauzulí je obrácené z pořadí v SQL.) Klauzule from určuje zdroj dat, where klauzule použije filtr a select klauzule určuje typ vrácených prvků. V této části jsou podrobně popsány všechny klauzule dotazu. Pro tuto chvíli je důležité, aby proměnná dotazu v LINQ sama neprobíla žádnou akci a nevracela žádná data. Ukládá jenom informace potřebné k vytvoření výsledků při spuštění dotazu v určitém pozdějším bodě. Další informace o tom, jak se vytvářejí dotazy, najdete v tématu Přehled standardních operátorů dotazů (C#).

Poznámka:

Dotazy lze také vyjádřit pomocí syntaxe metody. Další informace naleznete v tématu Syntaxe dotazu a syntaxe metody v LINQ.

Klasifikace standardních operátorů dotazů způsobem provádění

Implementace LINQ to Objects standardních metod operátoru dotazu se provádějí jedním ze dvou hlavních způsobů: okamžitě nebo odloženo. Operátory dotazů, které používají odložené spouštění, je možné navíc rozdělit do dvou kategorií: streamování a nestreamování.

Okamžité

Okamžité spuštění znamená, že je zdroj dat přečtený a operace se provede jednou. Všechny standardní operátory dotazu, které vrací skalární výsledek, se spustí okamžitě. Příklady takových dotazů jsou Count, Max, Average, a First. Tyto metody se provádějí bez explicitního foreach příkazu, protože samotný dotaz musí použít foreach k vrácení výsledku. Tyto dotazy vrátí jednu hodnotu, ne kolekci IEnumerable . Libovolný dotaz můžete vynutit okamžité spuštění pomocí Enumerable.ToList metod nebo Enumerable.ToArray metod. Okamžité spuštění poskytuje opakované použití výsledků dotazu, nikoli deklaraci dotazu. Výsledky se načtou jednou a pak se uloží pro budoucí použití. Následující dotaz vrátí počet sudých čísel ve zdrojovém poli:

var evenNumQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

int evenNumCount = evenNumQuery.Count();

Pokud chcete vynutit okamžité spuštění jakéhokoli dotazu a uložit jeho výsledky do mezipaměti, můžete volat ToList metody nebo ToArray metody.

List<int> numQuery2 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToArray();

Spuštění můžete také vynutit tak, že smyčku foreach umístíte hned za výraz dotazu. Voláním nebo ToArray také uložením ToList všech dat do mezipaměti v jednom objektu kolekce.

Odloženo

Odložené spuštění znamená, že operace není provedena v okamžiku v kódu, ve kterém je dotaz deklarován. Operace se provádí pouze v případě, že je proměnná dotazu vyčíslována, například pomocí foreach příkazu. Výsledky provádění dotazu závisí na obsahu zdroje dat, když se dotaz spustí, a ne při definování dotazu. Pokud je proměnná dotazu vyčíslována vícekrát, výsledky se můžou pokaždé lišit. Téměř všechny standardní operátory dotazu, jejichž návratový typ je IEnumerable<T> nebo IOrderedEnumerable<TElement> spouští odloženým způsobem. Odložené spuštění poskytuje možnost opakovaného použití dotazu, protože dotaz načte aktualizovaná data ze zdroje dat pokaždé, když se výsledky dotazu itehodnotují. Následující kód ukazuje příklad odloženého spuštění:

foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

Příkaz foreach je také tam, kde se načítají výsledky dotazu. Například v předchozím dotazu obsahuje proměnná num iterace každou hodnotu (jednu po druhé) ve vrácené sekvenci.

Vzhledem k tomu, že samotná proměnná dotazu nikdy neudržuje výsledky dotazu, můžete ji opakovaně spustit a načíst aktualizovaná data. Například samostatná aplikace může průběžně aktualizovat databázi. V aplikaci můžete vytvořit jeden dotaz, který načte nejnovější data, a můžete ho spustit v intervalech a načíst aktualizované výsledky.

Operátory dotazů, které používají odložené spuštění, je možné dále klasifikovat jako streamování nebo nestreaming.

Streamování

Operátory streamování nemusí před načtením prvků číst všechna zdrojová data. V době provádění operátor streamování provádí svou operaci u každého zdrojového prvku, protože je přečteno a v případě potřeby poskytuje prvek. Operátor streamování nadále čte zdrojové elementy, dokud nebude možné vytvořit výsledný prvek. To znamená, že za účelem vytvoření jednoho výsledného prvku může být přečteno více než jeden zdrojový prvek.

Nonstreaming

Operátory bezstreamingu musí číst všechna zdrojová data, aby mohly přinést výsledný prvek. Operace, jako je řazení nebo seskupení, spadají do této kategorie. V době provádění čtou nestreamovací operátory dotazu všechna zdrojová data, umístí je do datové struktury, provede operaci a výslednou část.

Tabulka klasifikace

Následující tabulka klasifikuje každou standardní metodu operátoru dotazu podle metody provádění.

Poznámka:

Pokud je operátor označený ve dvou sloupcích, jsou do operace zapojeny dvě vstupní sekvence a každá sekvence se vyhodnotí jinak. V těchto případech je to vždy první sekvence v seznamu parametrů, která se vyhodnocuje odloženým a streamovaným způsobem.

Standardní operátor dotazu Návratový typ Okamžité spuštění Odložené spuštění streamování Odložené provádění mimostreaming
Aggregate TSource X
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Average Jedna číselná hodnota X
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource? X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource? X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
Last TSource X
LastOrDefault TSource? X
LongCount Int64 X
Max Jedna číselná hodnota, TSourcenebo TResult? X
Min Jedna číselná hodnota, TSourcenebo TResult? X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable<TElement> X
OrderByDescending IOrderedEnumerable<TElement> X
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource? X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Sum Jedna číselná hodnota X
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ThenBy IOrderedEnumerable<TElement> X
ThenByDescending IOrderedEnumerable<TElement> X
ToArray TSource[] Pole X
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Where IEnumerable<T> X

LINQ to objects

"LINQ to Objects" odkazuje na použití dotazů LINQ s libovolnou IEnumerable nebo IEnumerable<T> kolekcí přímo. LinQ můžete použít k dotazování na všechny výčtové kolekce, například List<T>, Arraynebo Dictionary<TKey,TValue>. Kolekce může být uživatelem definovaná nebo typ vrácený rozhraním .NET API. V přístupu LINQ napíšete deklarativní kód, který popisuje, co chcete načíst. LinQ to Objects poskytuje skvělý úvod do programování pomocí LINQ.

Dotazy LINQ nabízejí tři hlavní výhody oproti tradičním foreach smyčkám:

  • Jsou stručnější a čitelnější, zejména při filtrování více podmínek.
  • Poskytují výkonné možnosti filtrování, řazení a seskupování s minimálním kódem aplikace.
  • Mohou být portovány do jiných zdrojů dat s malými nebo žádnými úpravami.

Čím složitější je operace, kterou chcete s daty provádět, tím větší výhodou je použití LINQ místo tradičních iteračních technik.

Ukládání výsledků dotazu do paměti

Dotaz je v podstatě sada instrukcí pro načtení a uspořádání dat. Dotazy se spouštějí opožděně, protože se vyžaduje každá další položka ve výsledku. foreach Při iteraci výsledků se položky vrátí jako přístup. Pokud chcete vyhodnotit dotaz a uložit jeho výsledky bez spuštění smyčky, stačí pro proměnnou foreach dotazu zavolat jednu z následujících metod:

Vrácený objekt kolekce byste měli přiřadit k nové proměnné při ukládání výsledků dotazu, jak je znázorněno v následujícím příkladu:

List<int> numbers = [1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20];

IEnumerable<int> queryFactorsOfFour =
    from num in numbers
    where num % 4 == 0
    select num;

// Store the results in a new variable
// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();

// Read and write from the newly created list to demonstrate that it holds data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

Viz také