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.

Tipp

Eine bequeme Zusammenfassung der Abfrageoperatoren im Tabellenformat, mit der Sie das Ausführungsverhalten eines Operators schnell identifizieren können, finden Sie unter Klassifizierung von Standardabfrageoperatoren nach Ausführung (C#).

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 wird als verzögerte Ausführung bezeichnet; d. h. die Abfrageausführung erfolgt einige Zeit, nachdem die Abfrage erstellt wurde. 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 (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

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 (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

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 dazu, welche Arten von Ausdrücken lokal ausgeführt werden, finden Sie unter Ausdrücke in LINQ to Entities Queries.

Literale und Parameter

Lokale Variablen, wie die orderID-Variable im folgenden Beispiel, werden auf dem Client ausgewertet.

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

Methodenparameter werden ebenfalls auf dem Client ausgewertet. Der unten der orderID-Methode übergebene MethodParameterExample-Parameter ist ein Beispiel dafür.

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

Umwandeln von Literalen auf dem Client

Umwandlungen von null in einen CLR-Typ werden auf dem Client ausgeführt:

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

Umwandlungen in einen Typ, wie ein Decimal, das NULL-Werte zulässt, werden auf dem Client ausgeführt:

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

Konstruktoren für Literale

Neue CLR-Typen, die konzeptionellen Modelltypen zugeordnet werden können, werden auf dem Client ausgeführt:

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

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 (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