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óconefalse
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