Provádění dotazů

Po vytvoření dotazu LINQ uživatelem se převede na strom příkazů. Příkazový strom je reprezentace dotazu, který je kompatibilní s Rozhraním Entity Framework. Příkazový strom se pak spustí ve zdroji dat. V době provádění dotazu se vyhodnocují všechny výrazy dotazu (tj. všechny komponenty dotazu), včetně výrazů, které se používají při materializaci výsledků.

V jakém okamžiku se výrazy dotazu provádějí, se mohou lišit. Dotazy LINQ se vždy provádějí, když je proměnná dotazu iterated, ne při vytvoření proměnné dotazu. Tomu se říká odložené spuštění. Dotaz můžete také vynutit, aby se spustil okamžitě, což je užitečné pro ukládání výsledků dotazu do mezipaměti. Toto je popsáno dále v tomto tématu.

Když se spustí dotaz LINQ to Entities, můžou se některé výrazy v dotazu spustit na serveru a některé části se můžou spouštět místně v klientovi. Vyhodnocení výrazu na straně klienta probíhá před spuštěním dotazu na serveru. Pokud se výraz vyhodnotí na klientovi, výsledek tohoto vyhodnocení se nahradí výrazem v dotazu a dotaz se pak spustí na serveru. Vzhledem k tomu, že dotazy se spouští ve zdroji dat, konfigurace zdroje dat přepíše chování zadané v klientovi. Například zpracování hodnot null a numerická přesnost závisí na nastavení serveru. Všechny výjimky vyvolané při provádění dotazů na serveru se předávají přímo klientovi.

Tip

Praktický přehled operátorů dotazů ve formátu tabulky, který umožňuje rychle identifikovat chování operátora při provádění, najdete v tématu Klasifikace standardních operátorů dotazů způsobem provádění (C#).

Odložené spuštění dotazu

V dotazu, který vrací posloupnost hodnot, proměnná dotazu sama nikdy neuchová výsledky dotazu a ukládá pouze příkazy dotazu. Provádění dotazu je odloženo, dokud se proměnná dotazu nepřečítá v foreach rámci smyčky nebo For Each smyčky. To se označuje jako odložené spuštění. To znamená, že provádění dotazu probíhá určitou dobu po vytvoření dotazu. To znamená, že dotaz můžete spouštět tak často, jak chcete. To je užitečné například v případě, že máte databázi, která se aktualizuje jinými aplikacemi. V aplikaci můžete vytvořit dotaz, který načte nejnovější informace a opakovaně spustí dotaz a vrátí aktualizované informace pokaždé.

Odložené spuštění umožňuje sloučení více dotazů nebo rozšíření dotazu. Když se dotaz rozšíří, upraví se tak, aby zahrnoval nové operace a případné spuštění bude odrážet změny. V následujícím příkladu první dotaz vrátí všechny produkty. Druhý dotaz rozšíří první pomocí k Where vrácení všech produktů velikosti "L":

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    IQueryable<Product> productsQuery =
        from p in context.Products
        select p;

    IQueryable<Product> largeProducts = productsQuery.Where(p => p.Size == "L");

    Console.WriteLine("Products of size 'L':");
    foreach (var product in largeProducts)
    {
        Console.WriteLine(product.Name);
    }
}
Using context As New AdventureWorksEntities()
    Dim productsQuery = _
        From p In context.Products _
        Select p

    Dim largeProducts = _
        productsQuery.Where(Function(p) p.Size = "L")

    Console.WriteLine("Products of size 'L':")
    For Each product In largeProducts
        Console.WriteLine(product.Name)
    Next
End Using

Po provedení dotazu budou všechny následné dotazy používat operátory LINQ v paměti. Iterace proměnné dotazu pomocí foreach příkazu nebo For Each volání jednoho z operátorů převodu LINQ způsobí okamžité spuštění. Mezi tyto operátory převodu patří: ToList, ToArray, ToLookupa ToDictionary.

Okamžité spuštění dotazu

Na rozdíl od odloženého provádění dotazů, které vytvářejí sekvenci hodnot, se dotazy, které vracejí jednu hodnotu, spustí okamžitě. Mezi příklady jednoúčelových dotazů patří Average, Count, Firsta Max. Tyto příkazy se spustí okamžitě, protože dotaz musí vytvořit sekvenci pro výpočet výsledku singletonu. Můžete také vynutit okamžité spuštění. To je užitečné, když chcete ukládat výsledky dotazu do mezipaměti. Pokud chcete vynutit okamžité spuštění dotazu, který nevygeneruje jednu hodnotu, můžete metodu, metodu ToDictionary nebo ToArray metodu volat ToList v dotazu nebo proměnné dotazu. Následující příklad používá metodu ToArray k okamžitému vyhodnocení sekvence do pole.

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    ObjectSet<Product> products = context.Products;

    Product[] prodArray = (
        from product in products
        orderby product.ListPrice descending
        select product).ToArray();

    Console.WriteLine("Every price from highest to lowest:");
    foreach (Product product in prodArray)
    {
        Console.WriteLine(product.ListPrice);
    }
}
Using context As New AdventureWorksEntities
    Dim products As ObjectSet(Of Product) = context.Products

    Dim prodArray As Product() = ( _
        From product In products _
        Order By product.ListPrice Descending _
        Select product).ToArray()

    Console.WriteLine("The list price from highest to lowest:")
    For Each prod As Product In prodArray
        Console.WriteLine(prod.ListPrice)
    Next
End Using

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

Spuštění úložiště

Obecně platí, že výrazy v LINQ to Entities se vyhodnocují na serveru a chování výrazu by nemělo být očekávané, aby dodržovaly sémantiku CLR (Common Language Runtime), ale ty ze zdroje dat. Existují však výjimky, například při spuštění výrazu v klientovi. To může způsobit neočekávané výsledky, například když jsou server a klient v různých časových pásmech.

Některé výrazy v dotazu se můžou spustit v klientovi. Obecně platí, že většina provádění dotazů se očekává na serveru. Kromě metod prováděných s prvky dotazu namapovanými na zdroj dat jsou často výrazy v dotazu, které lze spustit místně. Místní spuštění výrazu dotazu přináší hodnotu, kterou je možné použít při provádění dotazu nebo konstrukci výsledků.

Určité operace se vždy provádějí na klientovi, jako jsou vazby hodnot, dílčí výrazy, dílčí dotazy z uzavření a materializace objektů do výsledků dotazu. Čistým účinkem je, že tyto prvky (například hodnoty parametrů) nelze během provádění aktualizovat. Anonymní typy lze vytvořit vložené ve zdroji dat, ale nemělo by se předpokládat, že to uděláte. Vložené seskupení lze také vytvořit ve zdroji dat, ale nemělo by se předpokládat v každé instanci. Obecně platí, že není nejlepší, aby se žádné předpoklady o tom, co je vytvořené na serveru.

Tato část popisuje scénáře, ve kterých se kód spouští místně v klientovi. Další informace o tom, které typy výrazů se spouští místně, najdete v tématu Výrazy v LINQ to Entities Queries.

Literály a parametry

V klientovi se vyhodnocují místní proměnné, například orderID proměnná v následujícím příkladu.

int orderID = 51987;

IQueryable<SalesOrderHeader> salesInfo =
    from s in context.SalesOrderHeaders
    where s.SalesOrderID == orderID
    select s;
Dim orderID As Integer = 51987

Dim salesInfo = _
    From s In context.SalesOrderHeaders _
    Where s.SalesOrderID = orderID _
    Select s

Parametry metody se také vyhodnocují v klientovi. Příkladem orderID je parametr předaný do MethodParameterExample metody níže.

public static void MethodParameterExample(int orderID)
{
    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {

        IQueryable<SalesOrderHeader> salesInfo =
            from s in context.SalesOrderHeaders
            where s.SalesOrderID == orderID
            select s;

        foreach (SalesOrderHeader sale in salesInfo)
        {
            Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue);
        }
    }
}
Function MethodParameterExample(ByVal orderID As Integer)
    Using context As New AdventureWorksEntities()

        Dim salesInfo = _
            From s In context.SalesOrderHeaders _
            Where s.SalesOrderID = orderID _
            Select s

        Console.WriteLine("Sales order info:")
        For Each sale As SalesOrderHeader In salesInfo
            Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue)
        Next
    End Using

End Function

Přetypování literálů na klientovi

Přetypování typu null CLR se provádí v klientovi:

IQueryable<Contact> query =
    from c in context.Contacts
    where c.EmailAddress == (string)null
    select c;
Dim query = _
    From c In context.Contacts _
    Where c.EmailAddress = CType(Nothing, String) _
    Select c

Přetypování na typ, například nullable Decimal, se provádí na klientovi:

var weight = (decimal?)23.77;
IQueryable<Product> query =
    from product in context.Products
    where product.Weight == weight
    select product;
Dim weight = CType(23.77, Decimal?)
Dim query = _
    From product In context.Products _
    Where product.Weight = weight _
    Select product

Konstruktory pro literály

Nové typy CLR, které je možné mapovat na koncepční typy modelů, se spouští v klientovi:

var weight = new decimal(23.77);
IQueryable<Product> query =
    from product in context.Products
    where product.Weight == weight
    select product;
Dim weight = New Decimal(23.77)
Dim query = _
    From product In context.Products _
    Where product.Weight = weight _
    Select product

Na klientovi se také spouští nová pole.

Výjimky úložiště

Všechny chyby úložiště, ke kterým dochází během provádění dotazů, se předávají klientovi a nejsou mapovány ani zpracovány.

Konfigurace úložiště

Když se dotaz spustí v úložišti, konfigurace úložiště přepíše všechna chování klienta a sémantika úložiště se vyjadřuje pro všechny operace a výrazy. To může vést k rozdílu v chování mezi CLR a spouštění v oblastech, jako jsou porovnání s hodnotou null, řazení GUID, přesnost a přesnost operací zahrnujících nepřesné datové typy (například typy s plovoucí desetinnou čárkou nebo DateTime) a řetězcové operace. Při zkoumání výsledků dotazu je důležité mít na paměti.

Tady jsou například některé rozdíly v chování modulu CLR a SQL Serveru:

  • SQL Server objednává identifikátory GUID odlišně než CLR.

  • Při práci s typem Desetinné čárky na SQL Serveru mohou být také rozdíly v přesnosti výsledku. Důvodem jsou pevné požadavky na přesnost typu desítkového typu SQL Serveru. Například průměr Decimal hodnot 0,0, 0,0 a 1,0 je 0,333333333333333333333333333333333 v paměti v klientovi, ale 0,3333333 v úložišti (na základě výchozí přesnosti pro desetinný typ SQL Serveru).

  • Některé operace porovnání řetězců se také zpracovávají jinak v SQL Serveru než v CLR. Chování porovnání řetězců závisí na nastavení kolace na serveru.

  • Volání funkce nebo metody, pokud je součástí dotazu LINQ to Entities, jsou mapovány na kanonické funkce v Entity Frameworku, které se pak překládají do jazyka Transact-SQL a spouští se v databázi SQL Serveru. Existují případy, kdy se chování těchto mapovaných funkcí může lišit od implementace v knihovnách základních tříd. Například volání Contains, StartsWitha EndsWith metody s prázdným řetězcem jako parametr se vrátí true při spuštění v CLR, ale vrátí false při spuštění v SQL Serveru. Metoda EndsWith může také vrátit různé výsledky, protože SQL Server považuje dva řetězce za stejný, pokud se liší pouze na konci prázdného místa, zatímco CLR je považuje za nerovnající se. To je znázorněno v následujícím příkladu:

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    IQueryable<string> query = from p in context.Products
                               where p.Name == "Reflector"
                               select p.Name;

    IEnumerable<bool> q = query.Select(c => c.EndsWith("Reflector "));

    Console.WriteLine("LINQ to Entities returns: " + q.First());
    Console.WriteLine("CLR returns: " + "Reflector".EndsWith("Reflector "));
}
Using context As New AdventureWorksEntities()

    Dim query = _
        From p In context.Products _
        Where p.Name = "Reflector" _
        Select p.Name

    Dim q = _
        query.Select(Function(c) c.EndsWith("Reflector "))

    Console.WriteLine("LINQ to Entities returns: " & q.First())
    Console.WriteLine("CLR returns: " & "Reflector".EndsWith("Reflector "))
End Using