Abfrageausführung
Nachdem eine LINQ-Abfrage von einem Benutzer erstellt wurde, wird sie in eine Befehlsstruktur konvertiert. Eine Befehlsstruktur ist eine Darstellung einer Abfrage, die mit dem Entity Framework kompatibel ist. Die Befehlsstruktur wird dann für die Datenquelle ausgeführt. Während der Ausführung der Abfrage werden alle Abfrageausdrücke (d. h. alle Komponenten der Abfrage) ausgewertet, einschließlich der Ausdrücke, die für die Materialisierung der Ergebnisse verwendet werden.
An welchem Punkt Abfrageausdrücke ausgeführt werden ist unterschiedlich. LINQ-Abfragen werden nicht bei der Erstellung der Abfragevariablen, sondern beim Durchlaufen der Abfragevariablen ausgeführt. Dies wird als verzögerte Ausführung bezeichnet. Eine sofortige Ausführung der Abfrage kann auch erzwungen werden. Dies ist für die Zwischenspeicherung von Abfrageergebnissen sinnvoll. Dies wird weiter unten in diesem Thema beschrieben.
Beim Ausführen einer LINQ to Entities-Abfrage werden möglicherweise einige Ausdrücke auf dem Server und andere lokal auf dem Client ausgeführt. Ein Ausdruck wird auf dem Client ausgewertet, bevor die Abfrage auf dem Server ausgeführt wird. Wenn ein Ausdruck auf dem Client ausgewertet wird, wird dieser Ausdruck in der Abfrage durch das Ergebnis der Auswertung ersetzt. Anschließend wird die Abfrage auf dem Server ausgeführt. Da Abfragen für die Datenquelle ausgeführt werden, wird das auf dem Client festgelegte Verhalten von der Konfiguration der Datenquelle überschrieben. Zum Beispiel hängen die Behandlung von NULL-Werten und die numerische Genauigkeit von den Servereinstellungen ab. Alle Ausnahmen, die bei der Abfrageausführung auf dem Server ausgelöst wurden, werden direkt an den Client weitergegeben.
Verzögerte Abfrageausführung
In einer Abfrage, die eine Sequenz von Werten zurückgibt, enthält die Abfragevariable selbst niemals die Abfrageergebnisse, sondern immer nur die Abfragebefehle. Die Ausführung der Abfrage wird verzögert, bis die Abfragevariable in einer foreach- oder For Each-Schleife durchlaufen wird. Dies ist auch als verzögerte Ausführung bekannt, das heißt, die Abfrage wird zu einem späteren Zeitpunkt ausgeführt und nicht sofort bei ihrer Erstellung. Auf diese Weise können Sie die Abfrage so häufig ausführen, wie Sie dies wünschen. Dies bietet sich z. B. dann an, wenn Sie eine Datenbank haben, die von anderen Anwendungen aktualisiert wird. Sie können in Ihrer Anwendung eine Abfrage erstellen, mit der die neuesten Informationen abgerufen werden, und diese Abfrage wiederholt ausführen, wobei jedes Mal die aktualisierten Informationen zurückgegeben werden.
Die verzögerte Ausführung ermöglicht die Kombination mehrerer Abfragen oder die Erweiterung einer bestehenden Abfrage. durch das Hinzufügen neuer Operationen. Die Änderungen werden dann bei der Ausführung der Abfrage berücksichtigt. Im folgenden Beispiel gibt die erste Abfrage alle Produkte zurück. Die zweite Abfrage erweitert die erste, indem sie Where verwendet, um alle Produkte der Größe "L" zurückzugeben:
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
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);
}
}
Nachdem eine Abfrage ausgeführt wurde, verwenden alle folgenden Abfragen die LINQ-Operatoren im Arbeitsspeicher. Durch das Durchlaufen der Abfragevariable durch die Verwendung einer foreach- oder For Each-Anweisung oder das Aufrufen eines der LINQ-Konvertierungsoperatoren wird die sofortige Ausführung verursacht. Dabei kann es sich um folgende Konvertierungsoperatoren handeln: ToList, ToArray, ToLookup und ToDictionary.
Unmittelbare Abfrageausführung
Im Gegensatz zur verzögerten Ausführung von Abfragen, die eine Sequenz von Werten zurückgeben, werden Abfragen, die einen Singleton-Wert zurückgeben, sofort ausgeführt. Einige Beispiele für SINGLETON-Abfragen sind Average, Count, First und Max. Diese werden sofort ausgeführt, da die Abfrage eine Sequenz für die Berechnung des Singleton-Ergebnisses erzeugen muss. Eine unmittelbare Ausführung kann auch erzwungen werden. Dies ist nützlich, wenn die Ergebnisse einer Abfrage zwischengespeichert werden sollen. Zur Erzwingung der sofortigen Ausführung einer Abfrage, die keinen Singleton-Wert zurückgibt, kann die ToList-Methode, die ToDictionary-Methode oder die ToArray-Methode für eine Abfrage oder eine Abfragevariable aufgerufen werden. Im folgenden Beispiel wird die ToArray-Methode verwendet, um eine Sequenz unmittelbar in ein Array auszuwerten.
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
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);
}
}
Die Ausführung kann auch erzwungen werden, indem die foreach-Schleife oder die For Each-Schleife unmittelbar hinter den Abfrageausdruck gestellt wird. Mit einem Aufruf von ToList oder ToArray werden jedoch alle Daten in einem einzelnen Auflistungsobjekt zwischengespeichert.
Speicherausführung
Im Allgemeinen werden Ausdrücke in LINQ to Entities auf dem Server ausgewertet, und das Verhalten der Ausdrücke richtet sich nicht nach der CLR-Semantik, sondern nach der Semantik der Datenquelle. Dies gilt jedoch nicht, wenn der Ausdruck z. B. auf dem Client ausgeführt wird. Dies kann zu unerwarteten Ergebnissen führen, wenn sich beispielsweise Server und Client in unterschiedlichen Zeitzonen befinden.
Einige Ausdrücke in der Abfrage werden möglicherweise auf dem Client ausgeführt. Im Allgemeinen findet der größte Teil der Abfrageausführung auf dem Server statt. Neben Methoden, die für der Datenquelle zugeordnete Abfrageelemente ausgeführt werden, gibt es häufig Ausdrücke in der Abfrage, die lokal ausgeführt werden können. Bei der lokalen Ausführung eines Abfrageausdrucks wird ein Wert zurückgegeben, der für die Ausführung der Abfrage oder die Konstruktion des Ergebnisses verwendet werden kann.
Bestimmte Operationen werden stets auf dem Client ausgeführt. Dazu gehören die Bindung von Werten, Unterausdrücken und Unterabfragen von Abschlüssen sowie die Materialisierung von Objekten in Abfrageergebnisse. Das bedeutet, dass diese Elemente (beispielsweise Parameterwerte) während der Ausführung nicht aktualisiert werden können. Anonyme Typen können inline in der Datenquelle erstellt werden. Davon sollte jedoch nicht ausgegangen werden. Inlinegruppierungen können ebenfalls in der Datenquelle erstellt werden. Davon sollte jedoch ebenfalls nicht in jedem Fall ausgegangen werden. Im Allgemeinen sollten keine Annahmen darüber gemacht werden, was auf dem Server erstellt wird.
In diesem Abschnitt werden die Szenarios, in denen Code lokal auf dem Client ausgeführt wird, beschrieben. Weitere Informationen darüber, welche Typen von Ausdrücken lokal ausgeführt werden, finden Sie unter Ausdrücke in LINQ to Entities-Abfragen.
Literale und Parameter
Lokale Variablen, wie die orderID
-Variable im folgenden Beispiel, werden auf dem Client ausgewertet.
Dim orderID As Integer = 51987
Dim salesInfo = _
From s In context.SalesOrderHeaders _
Where s.SalesOrderID = orderID _
Select s
int orderID = 51987;
IQueryable<SalesOrderHeader> salesInfo =
from s in context.SalesOrderHeaders
where s.SalesOrderID == orderID
select s;
Methodenparameter werden ebenfalls auf dem Client ausgewertet. Der unten der MethodParameterExample
-Methode übergebene orderID
-Parameter ist ein Beispiel dafür.
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
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);
}
}
}
Umwandeln von Literalen auf dem Client
Umwandlungen von NULL in einen CLR-Typ werden auf dem Client ausgeführt:
Dim query = _
From c In context.Contacts _
Where c.EmailAddress = CType(Nothing, String) _
Select c
IQueryable<Contact> query =
from c in context.Contacts
where c.EmailAddress == (string)null
select c;
Umwandlungen in einen Typ, wie ein Decimal, das NULL-Werte zulässt, werden auf dem Client ausgeführt:
Dim weight = CType(23.77, Decimal?)
Dim query = _
From product In context.Products _
Where product.Weight = weight _
Select product
var weight = (decimal?)23.77;
IQueryable<Product> query =
from product in context.Products
where product.Weight == weight
select product;
Konstruktoren für Literale
Neue CLR-Typen, die konzeptionellen Modelltypen zugeordnet werden können, werden auf dem Client ausgeführt:
Dim weight = New Decimal(23.77)
Dim query = _
From product In context.Products _
Where product.Weight = weight _
Select product
var weight = new decimal(23.77);
IQueryable<Product> query =
from product in context.Products
where product.Weight == weight
select product;
Neue Arrays werden ebenfalls auf dem Client ausgeführt.
Speicherausnahmen
Während der Abfrageausführung auftretende Speicherfehler werden an den Client übergeben und nicht zugeordnet oder behandelt.
Speicherkonfiguration
Bei der Abfrageausführung im Speicher überschreibt die Speicherkonfiguration das Clientverhalten, und für alle Operationen und Ausdrücke wird Speichersemantik verwendet. Das kann zu unterschiedlichem Verhalten von CLR- und Speicherausführung in Bereichen wie NULL-Vergleichen, GUID-Sortierung, Genauigkeit von Operationen mit nicht-präzisen Datentypen (wie Gleitkommatypen oder DateTime) sowie Zeichenfolgenoperationen führen. Bei der Beurteilung von Abfrageergebnissen sollten diese Punkte beachtet werden.
Folgende Beispiele zeigen einige Unterschiede im Verhalten zwischen der CLR und SQL Server:
SQL Server sortiert GUIDs anders als die CLR.
Es können auch Unterschiede in der Ergebnisgenauigkeit beim Arbeiten mit dem Decimal-Typ auf SQL Server auftreten. Der Grund dafür sind die Anforderungen fester Präzision des Dezimaltyps von SQL Server. Beispielsweise ist der Durchschnitt der Decimal-Werte 0,0 und 0,0 und 1,0 im Arbeitsspeicher des Clients 0,3333333333333333333333333333 und im Speicher 0,333333 (für die Standardpräzision des Dezimaltyps von SQL Server).
Einige Zeichenfolgenvergleichsvorgänge werden in SQL Server ebenfalls anders behandelt als in der CLR. Das Verhalten bei Zeichenfolgenvergleichen hängt von den Sortiereinstellungen auf dem Server ab.
Funktions- oder Methodenaufrufe, die in einer LINQ to Entities-Abfrage enthalten sind, werden kanonischen Funktionen im Entity Framework zugeordnet, die wiederum in Transact-SQL übersetzt und in der SQL Server-Datenbank ausgeführt werden. Es gibt Fälle, in denen sich das Verhalten dieser zugeordneten Funktionen von dem der Implementierung in den Basisklassenbibliotheken unterscheidet. Ein Aufruf der Contains, StartsWith-Methode und der EndsWith-Methode mit einer leeren Zeichenfolge als Parameter gibt bei der Ausführung in der CLR true zurück und bei der Ausführung in SQL Server false. Die EndsWith-Methode kann ebenfalls unterschiedliche Ergebnisse zurückgeben, da zwei Zeichenfolgen, die sich nur in nachfolgenden Leerzeichen unterscheiden, in SQL Server als gleich aufgefasst werden, in der CLR jedoch nicht. Dies wird im folgenden Beispiel illustriert:
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
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 "));
}