Udostępnij za pośrednictwem


Wykonywanie zapytania

Po utworzeniu zapytania LINQ przez użytkownika jest ono konwertowane na drzewo poleceń. Drzewo poleceń to reprezentacja zapytania zgodnego z programem Entity Framework. Drzewo poleceń jest następnie wykonywane względem źródła danych. W czasie wykonywania zapytania są obliczane wszystkie wyrażenia zapytania (czyli wszystkie składniki zapytania), w tym wyrażenia, które są używane w materializacji wyników.

W jakim momencie wykonywane są wyrażenia zapytań, mogą się różnić. Zapytania LINQ są zawsze wykonywane, gdy zmienna kwerendy jest iterowana, a nie podczas tworzenia zmiennej kwerendy. Jest to nazywane odroczonym wykonaniem. Możesz również wymusić natychmiastowe wykonanie zapytania, co jest przydatne w przypadku buforowania wyników zapytania. Opisano to w dalszej części tego tematu.

Po wykonaniu zapytania LINQ to Entities niektóre wyrażenia w zapytaniu mogą być wykonywane na serwerze, a niektóre części mogą być wykonywane lokalnie na kliencie. Ocena po stronie klienta wyrażenia odbywa się przed wykonaniem zapytania na serwerze. Jeśli wyrażenie jest obliczane na kliencie, wynik tej oceny jest zastępowany wyrażeniem w zapytaniu, a zapytanie jest następnie wykonywane na serwerze. Ponieważ zapytania są wykonywane w źródle danych, konfiguracja źródła danych zastępuje zachowanie określone w kliencie. Na przykład obsługa wartości null i precyzja liczbowa zależą od ustawień serwera. Wszelkie wyjątki zgłaszane podczas wykonywania zapytania na serwerze są przekazywane bezpośrednio do klienta.

Napiwek

Aby uzyskać wygodne podsumowanie operatorów zapytań w formacie tabeli, co pozwala szybko zidentyfikować zachowanie operatora, zobacz Klasyfikacja standardowych operatorów zapytań według sposobu wykonywania (C#).

Odroczone wykonywanie zapytania

W zapytaniu, które zwraca sekwencję wartości, zmienna kwerendy nigdy nie przechowuje wyników zapytania i przechowuje tylko polecenia zapytania. Wykonanie zapytania jest odroczone, dopóki zmienna kwerendy nie zostanie iterowana w foreach pętli lub For Each . Jest to nazywane wykonywaniem odroczonym. Oznacza to, że wykonywanie zapytań występuje jakiś czas po utworzeniu zapytania. Oznacza to, że można wykonać zapytanie tak często, jak chcesz. Jest to przydatne, gdy na przykład masz bazę danych, która jest aktualizowana przez inne aplikacje. W aplikacji można utworzyć zapytanie, aby pobrać najnowsze informacje i wielokrotnie wykonywać zapytanie, zwracając zaktualizowane informacje za każdym razem.

Odroczone wykonywanie umożliwia łączenie wielu zapytań lub rozszerzanie zapytania. Gdy zapytanie zostanie rozszerzone, zostanie zmodyfikowane tak, aby obejmowało nowe operacje, a ostateczne wykonanie będzie odzwierciedlać zmiany. W poniższym przykładzie pierwsze zapytanie zwraca wszystkie produkty. Drugie zapytanie rozszerza pierwszy przy użyciu polecenia Where , aby zwrócić wszystkie produkty o rozmiarze "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 wykonaniu zapytania wszystkie kolejne zapytania będą używać operatorów LINQ w pamięci. Iterowanie zmiennej zapytania przy użyciu instrukcji foreach lub For Each przez wywołanie jednego z operatorów konwersji LINQ spowoduje natychmiastowe wykonanie. Te operatory konwersji obejmują następujące elementy: ToList, , ToArrayToLookupi ToDictionary.

Natychmiastowe wykonywanie zapytań

W przeciwieństwie do odroczonego wykonywania zapytań, które generują sekwencję wartości, zapytania zwracające pojedynczą wartość są wykonywane natychmiast. Niektóre przykłady pojedynczych zapytań to Average, Count, Firsti Max. Są one wykonywane natychmiast, ponieważ zapytanie musi utworzyć sekwencję w celu obliczenia pojedynczego wyniku. Możesz również wymusić natychmiastowe wykonanie. Jest to przydatne, gdy chcesz buforować wyniki zapytania. Aby wymusić natychmiastowe wykonanie zapytania, które nie generuje pojedynczej wartości, można wywołać ToList metodę, ToDictionary metodę lub metodę w kwerendzie lub ToArray zmiennej kwerendy. W poniższym przykładzie użyto ToArray metody , aby natychmiast ocenić sekwencję w tablicy.

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

Można również wymusić wykonywanie, umieszczając pętlę foreach lub For Each bezpośrednio po wyrażeniu zapytania, ale wywołując ToList lub ToArray buforując wszystkie dane w jednym obiekcie kolekcji.

Wykonywanie magazynu

Ogólnie rzecz biorąc, wyrażenia w linQ to Entities są oceniane na serwerze, a zachowanie wyrażenia nie powinno być zgodne z semantykami środowiska uruchomieniowego języka wspólnego (CLR), ale zachowaniem źródła danych. Istnieją jednak wyjątki, takie jak w przypadku wykonywania wyrażenia na kliencie. Może to spowodować nieoczekiwane wyniki, na przykład gdy serwer i klient znajdują się w różnych strefach czasowych.

Niektóre wyrażenia w zapytaniu mogą być wykonywane na kliencie. Ogólnie rzecz biorąc, na serwerze oczekuje się, że większość wykonań zapytań. Oprócz metod wykonywanych względem elementów zapytania zamapowanych na źródło danych często występują wyrażenia w zapytaniu, które można wykonać lokalnie. Lokalne wykonywanie wyrażenia zapytania daje wartość, która może być używana w wykonywaniu zapytania lub konstruowaniu wyników.

Niektóre operacje są zawsze wykonywane na kliencie, takie jak powiązanie wartości, wyrażenia podrzędne, zapytania podrzędne z zamknięcia i materializacja obiektów w wynikach zapytania. Efektem netto jest to, że te elementy (na przykład wartości parametrów) nie mogą być aktualizowane podczas wykonywania. Typy anonimowe można tworzyć w tekście w źródle danych, ale nie należy tego zakładać. Grupowania wbudowane można również utworzyć w źródle danych, ale nie należy zakładać tego w każdym wystąpieniu. Ogólnie rzecz biorąc, najlepiej nie podjąć żadnych założeń dotyczących tego, co jest skonstruowane na serwerze.

W tej sekcji opisano scenariusze, w których kod jest wykonywany lokalnie na kliencie. Aby uzyskać więcej informacji na temat typów wyrażeń wykonywanych lokalnie, zobacz Wyrażenia w zapytaniach LINQ to Entities.

Literały i parametry

Zmienne lokalne, takie jak zmienna orderID w poniższym przykładzie, są oceniane na kliencie.

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 są również oceniane na kliencie. Parametr orderID przekazany do MethodParameterExample metody poniżej jest przykładem.

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

Rzutowanie literałów na kliencie

Rzutowanie z null do typu CLR jest wykonywane na kliencie:

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

Rzutowanie do typu, takiego jak dopuszczający Decimalwartość null, jest wykonywane na kliencie:

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 literałów

Nowe typy CLR, które można mapować na typy modeli koncepcyjnych, są wykonywane na kliencie:

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 kliencie są również wykonywane nowe tablice.

Przechowywanie wyjątków

Wszelkie błędy magazynu, które występują podczas wykonywania zapytania, są przekazywane do klienta i nie są mapowane ani obsługiwane.

Konfiguracja magazynu

Gdy zapytanie jest wykonywane w magazynie, konfiguracja magazynu zastępuje wszystkie zachowania klientów, a semantyka magazynu jest wyrażana dla wszystkich operacji i wyrażeń. Może to spowodować różnicę w zachowaniu między clR i przechowywaniem wykonywania w obszarach, takich jak porównania wartości null, kolejność identyfikatora GUID, precyzja i dokładność operacji obejmujących niedokładne typy danych (takie jak typy zmiennoprzecinkowe lub DateTimeoperacje ciągów). Należy pamiętać o tym podczas badania wyników zapytania.

Na przykład poniżej przedstawiono pewne różnice w zachowaniu środowiska CLR i programu SQL Server:

  • Identyfikatory GUID programu SQL Server są porządkowane inaczej niż clR.

  • Podczas pracy z typem dziesiętny w programie SQL Server mogą również występować różnice w precyzji wynikowej. Jest to spowodowane stałymi wymaganiami dokładności typu dziesiętnego programu SQL Server. Na przykład średnia Decimal wartości 0,0, 0,0 i 1,0 wynosi 0,33333333333333333333333333333333333 w pamięci na kliencie, ale 0,3333333 w magazynie (na podstawie domyślnej dokładności dla typu dziesiętnego programu SQL Server).

  • Niektóre operacje porównania ciągów są również obsługiwane inaczej w programie SQL Server niż w środowisku CLR. Zachowanie porównania ciągów zależy od ustawień sortowania na serwerze.

  • Wywołania funkcji lub metody, jeśli są uwzględniane w zapytaniu LINQ to Entities, są mapowane na funkcje kanoniczne w programie Entity Framework, które następnie są tłumaczone na język Transact-SQL i wykonywane w bazie danych programu SQL Server. Istnieją przypadki, gdy zachowanie tych zamapowanych funkcji może różnić się od implementacji w bibliotekach klas bazowych. Na przykład wywołanie Containsmetod , StartsWithi EndsWith z pustym ciągiem jako parametrem zostanie zwrócone true po wykonaniu w środowisku CLR, ale zostanie zwrócone false po wykonaniu w programie SQL Server. Metoda EndsWith może również zwracać różne wyniki, ponieważ program SQL Server uważa, że dwa ciągi są równe, jeśli różnią się tylko końcowym białym znakiem, podczas gdy clR uważa, że nie są równe. Jest to zilustrowane w poniższym przykładzie:

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