Kompilierte Abfragen (LINQ to Entities)

Wenn eine Anwendung im Entity Framework häufig strukturell ähnliche Abfragen ausführt, kann in vielen Fällen die Leistung gesteigert werden, indem die Abfrage einmal kompiliert und anschließend mehrmals mit verschiedenen Parametern ausgeführt wird. So können beispielsweise mithilfe einer Anwendung alle Kunden in einer bestimmten Stadt abgerufen werden, wobei die Stadt zur Laufzeit vom Benutzer in einem Formular festgelegt wird. Für derartige Aufgaben unterstützt LINQ to Entities die Verwendung kompilierter Abfragen.

Ab .NET Framework 4.5 werden LINQ-Abfragen automatisch zwischengespeichert. Sie können jedoch weiterhin kompilierte LINQ-Abfragen verwenden, um diesen Aufwand in späteren Ausführungen zu reduzieren. Kompilierte Abfragen können effizienter als LINQ-Abfragen sein, die automatisch zwischengespeichert werden. LINQ to Entities-Abfragen, die den Enumerable.Contains-Operator auf Auflistungen im Arbeitsspeicher anwenden, werden nicht automatisch zwischengespeichert. Darüber hinaus ist das Parametrisieren von Auflistungen im Arbeitsspeicher in kompilierten LINQ-Abfragen nicht zulässig.

Die CompiledQuery-Klasse ermöglicht das Kompilieren und Zwischenspeichern von Abfragen zur Wiederverwendung. Diese Klasse enthält eine CompiledQuery-Methode einer Compile mit mehreren Überladungen. Rufen Sie die Compile-Methode auf, um einen neuen Delegaten für die Darstellung der kompilierten Abfrage zu erstellen. Die mit einem Compile und Parameterwerten ausgestatteten ObjectContext-Methoden geben einen Delegat zurück, der ein Ergebnis ausgibt (z. B. eine IQueryable<T>-Instanz). Die Abfrage wird nur einmal während der ersten Ausführung kompiliert. Die für die Abfrage zur Kompilierungszeit festgelegten Mergeoptionen können später nicht geändert werden. Nachdem die Abfrage kompiliert ist, können Sie nur Parameter mit einem primitiven Typ angeben. Sie können keine Teile der Abfrage ersetzen, die das generierte SQL ändern. Weitere Informationen finden Sie unter EF-Zusammenführungsoptionen und kompilierte Abfragen.

Der LINQ to Entities-Abfrageausdruck, der von der Compile-Methode der CompiledQuery kompiliert wird, wird durch einen der generischen Func-Delegaten, z. B. Func<T1,T2,T3,T4,TResult>, dargestellt. Der Abfrageausdruck kann höchstens einen ObjectContext-Parameter, einen Rückgabeparameter und 16 Abfrageparameter kapseln. Sind mehr als 16 Abfrageparameter erforderlich, können Sie eine Struktur erstellen, deren Eigenschaften Abfrageparameter darstellen. Sie können anschließend die Eigenschaften für die Struktur im Abfrageausdruck verwenden, nachdem die Eigenschaften festgelegt wurden.

Beispiel 1

Im folgenden Beispiel wird eine Abfrage kompiliert und anschließend aufgerufen, die einen Decimal-Eingabeparameter akzeptiert und eine Auftragssequenz zurückgibt, in der der fällige Gesamtbetrag 200,00 US-Dollar oder mehr beträgt:

static readonly Func<AdventureWorksEntities, Decimal, IQueryable<SalesOrderHeader>> s_compiledQuery2 =
    CompiledQuery.Compile<AdventureWorksEntities, Decimal, IQueryable<SalesOrderHeader>>(
            (ctx, total) => from order in ctx.SalesOrderHeaders
                            where order.TotalDue >= total
                            select order);

static void CompiledQuery2()
{
    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {
        Decimal totalDue = 200.00M;

        IQueryable<SalesOrderHeader> orders = s_compiledQuery2.Invoke(context, totalDue);

        foreach (SalesOrderHeader order in orders)
        {
            Console.WriteLine("ID: {0}  Order date: {1} Total due: {2}",
                order.SalesOrderID,
                order.OrderDate,
                order.TotalDue);
        }
    }
}
ReadOnly s_compQuery2 As Func(Of AdventureWorksEntities, Decimal, IQueryable(Of SalesOrderHeader)) = _
    CompiledQuery.Compile(Of AdventureWorksEntities, Decimal, IQueryable(Of SalesOrderHeader))( _
                Function(ctx As AdventureWorksEntities, total As Decimal) _
                    From order In ctx.SalesOrderHeaders _
                    Where (order.TotalDue >= total) _
                    Select order)

Sub CompiledQuery2()
    Using context As New AdventureWorksEntities()

        Dim totalDue As Decimal = 200.0

        Dim orders As IQueryable(Of SalesOrderHeader) = s_compQuery2.Invoke(context, totalDue)

        For Each order In orders
            Console.WriteLine("ID: {0}  Order date: {1} Total due: {2}", _
                                    order.SalesOrderID, _
                                    order.OrderDate, _
                                    order.TotalDue)
        Next
    End Using
End Sub

Beispiel 2

Im folgenden Beispiel wird eine Abfrage kompiliert und anschließend aufgerufen, die eine ObjectQuery<T>-Instanz zurückgibt:

static readonly Func<AdventureWorksEntities, ObjectQuery<SalesOrderHeader>> s_compiledQuery1 =
    CompiledQuery.Compile<AdventureWorksEntities, ObjectQuery<SalesOrderHeader>>(
            ctx => ctx.SalesOrderHeaders);

static void CompiledQuery1_MQ()
{

    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {
        IQueryable<SalesOrderHeader> orders = s_compiledQuery1.Invoke(context);

        foreach (SalesOrderHeader order in orders)
            Console.WriteLine(order.SalesOrderID);
    }
}
ReadOnly s_compQuery1 As Func(Of AdventureWorksEntities, ObjectQuery(Of SalesOrderHeader)) = _
    CompiledQuery.Compile(Of AdventureWorksEntities, ObjectQuery(Of SalesOrderHeader))( _
                Function(ctx) ctx.SalesOrderHeaders)

Sub CompiledQuery1_MQ()

    Using context As New AdventureWorksEntities()

        Dim orders As ObjectQuery(Of SalesOrderHeader) = s_compQuery1.Invoke(context)

        For Each order In orders
            Console.WriteLine(order.SalesOrderID)
        Next

    End Using
End Sub

Beispiel 3

Im folgenden Beispiel wird eine Abfrage kompiliert und anschließend aufgerufen, die den Durchschnitt der Produktlistenpreise als Decimal-Wert zurückgibt:

static readonly Func<AdventureWorksEntities, Decimal> s_compiledQuery3MQ = CompiledQuery.Compile<AdventureWorksEntities, Decimal>(
            ctx => ctx.Products.Average(product => product.ListPrice));

static void CompiledQuery3_MQ()
{

    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {
        Decimal averageProductPrice = s_compiledQuery3MQ.Invoke(context);

        Console.WriteLine("The average of the product list prices is $: {0}", averageProductPrice);
    }
}
Using context As New AdventureWorksEntities()
    Dim compQuery = CompiledQuery.Compile(Of AdventureWorksEntities, Decimal)( _
            Function(ctx) ctx.Products.Average(Function(Product) Product.ListPrice))

    Dim averageProductPrice As Decimal = compQuery.Invoke(context)

    Console.WriteLine("The average of the product list prices is $: {0}", averageProductPrice)
End Using

Beispiel 4

Im folgenden Beispiel wird eine Abfrage kompiliert und anschließend aufgerufen, der ein String-Eingabeparameter übergeben wird und die einen Contact zurückgibt, dessen E-Mail-Adresse mit der angegebenen Zeichenfolge beginnt:

static readonly Func<AdventureWorksEntities, string, Contact> s_compiledQuery4MQ =
    CompiledQuery.Compile<AdventureWorksEntities, string, Contact>(
            (ctx, name) => ctx.Contacts.First(contact => contact.EmailAddress.StartsWith(name)));

static void CompiledQuery4_MQ()
{
    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {
        string contactName = "caroline";
        Contact foundContact = s_compiledQuery4MQ.Invoke(context, contactName);

        Console.WriteLine("An email address starting with 'caroline': {0}",
            foundContact.EmailAddress);
    }
}
Using context As New AdventureWorksEntities()
    Dim compQuery = CompiledQuery.Compile(Of AdventureWorksEntities, String, Contact)( _
            Function(ctx, name) ctx.Contacts.First(Function(contact) contact.EmailAddress.StartsWith(name)))

    Dim contactName As String = "caroline"
    Dim foundContact As Contact = compQuery.Invoke(context, contactName)

    Console.WriteLine("An email address starting with 'caroline': {0}", _
            foundContact.EmailAddress)
End Using

Beispiel 5

Im folgenden Beispiel wird eine Abfrage kompiliert und anschließend aufgerufen, der ein DateTime-Eingabeparameter und ein Decimal-Eingabeparameter übergeben wird und die eine Sequenz von Aufträgen zurückgibt, in denen das Auftragsdatum nach dem 3. März 2008 liegt und der fällige Gesamtbetrag weniger als 300,00 Dollar beträgt:

static readonly Func<AdventureWorksEntities, DateTime, Decimal, IQueryable<SalesOrderHeader>> s_compiledQuery5 =
    CompiledQuery.Compile<AdventureWorksEntities, DateTime, Decimal, IQueryable<SalesOrderHeader>>(
            (ctx, orderDate, totalDue) => from product in ctx.SalesOrderHeaders
                                          where product.OrderDate > orderDate
                                             && product.TotalDue < totalDue
                                          orderby product.OrderDate
                                          select product);

static void CompiledQuery5()
{
    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {
        DateTime date = new DateTime(2003, 3, 8);
        Decimal amountDue = 300.00M;

        IQueryable<SalesOrderHeader> orders = s_compiledQuery5.Invoke(context, date, amountDue);

        foreach (SalesOrderHeader order in orders)
        {
            Console.WriteLine("ID: {0} Order date: {1} Total due: {2}", order.SalesOrderID, order.OrderDate, order.TotalDue);
        }
    }
}
ReadOnly s_compQuery5 = _
   CompiledQuery.Compile(Of AdventureWorksEntities, DateTime, Decimal, IQueryable(Of SalesOrderHeader))( _
                Function(ctx, orderDate, totalDue) From product In ctx.SalesOrderHeaders _
                                                   Where product.OrderDate > orderDate _
                                                      And product.TotalDue < totalDue _
                                                   Order By product.OrderDate _
                                                   Select product)
Sub CompiledQuery5()

    Using context As New AdventureWorksEntities()

        Dim orderedAfterDate As DateTime = New DateTime(2003, 3, 8)
        Dim amountDue As Decimal = 300.0

        Dim orders As IQueryable(Of SalesOrderHeader) = _
            s_compQuery5.Invoke(context, orderedAfterDate, amountDue)

        For Each order In orders
            Console.WriteLine("ID: {0} Order date: {1} Total due: {2}", _
                              order.SalesOrderID, order.OrderDate, order.TotalDue)
        Next

    End Using
End Sub

Beispiel 6

Im folgenden Beispiel wird eine Abfrage kompiliert und anschließend aufgerufen, der ein DateTime-Eingabeparameter übergeben wird und die eine Sequenz von Aufträgen zurückgibt, in denen das Bestelldatum nach dem 8. März 2004 liegt: Diese Abfrage gibt die Bestellinformationen als Sequenz anonymer Typen zurück. Anonyme Typen werden vom Compiler abgeleitet, sodass in der CompiledQuery-Methode von Compile keine Typparameter angegeben werden können. Der Typ wird in der Abfrage selbst definiert.

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    var compiledQuery = CompiledQuery.Compile((AdventureWorksEntities ctx, DateTime orderDate) =>
        from order in ctx.SalesOrderHeaders
        where order.OrderDate > orderDate
        select new {order.OrderDate, order.SalesOrderID, order.TotalDue});

    DateTime date = new DateTime(2004, 3, 8);
    var results = compiledQuery.Invoke(context, date);

    foreach (var order in results)
    {
        Console.WriteLine("ID: {0} Order date: {1} Total due: {2}", order.SalesOrderID, order.OrderDate, order.TotalDue);
    }
}
Using context As New AdventureWorksEntities()
    Dim compQuery = CompiledQuery.Compile( _
            Function(ctx As AdventureWorksEntities, orderDate As DateTime) _
                From order In ctx.SalesOrderHeaders _
                Where order.OrderDate > orderDate _
                Select New With {order.OrderDate, order.SalesOrderID, order.TotalDue})

    Dim orderedAfterDate As DateTime = New DateTime(2004, 3, 8)

    Dim orders = compQuery.Invoke(context, orderedAfterDate)

    For Each order In orders
        Console.WriteLine("ID: {0} Order date: {1} Total due: {2}", _
                          order.SalesOrderID, order.OrderDate, order.TotalDue)
    Next

End Using

Beispiel 7

Im folgenden Beispiel wird eine Abfrage kompiliert und anschließend aufgerufen, die einen Eingabeparameter mit einer benutzerdefinierten Struktur akzeptiert und eine Sequenz von Aufträgen zurückgibt. Die Struktur definiert die Abfrageparameter für Startdatum, Enddatum und den fälligen Gesamtbetrag. Die Abfrage gibt die zwischen dem 3. und 8. März 2003 versendeten Bestellungen mit einem fälligen Gesamtbetrag von über 700,00 US-Dollar zurück.

static Func<AdventureWorksEntities, MyParams, IQueryable<SalesOrderHeader>> s_compiledQuery =
    CompiledQuery.Compile<AdventureWorksEntities, MyParams, IQueryable<SalesOrderHeader>>(
            (ctx, myparams) => from sale in ctx.SalesOrderHeaders
                               where sale.ShipDate > myparams.startDate && sale.ShipDate < myparams.endDate
                               && sale.TotalDue > myparams.totalDue
                               select sale);
static void CompiledQuery7()
{

    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {
        MyParams myParams = new MyParams();
        myParams.startDate = new DateTime(2003, 3, 3);
        myParams.endDate = new DateTime(2003, 3, 8);
        myParams.totalDue = 700.00M;

        IQueryable<SalesOrderHeader> sales = s_compiledQuery.Invoke(context, myParams);

        foreach (SalesOrderHeader sale in sales)
        {
            Console.WriteLine("ID: {0}", sale.SalesOrderID);
            Console.WriteLine("Ship date: {0}", sale.ShipDate);
            Console.WriteLine("Total due: {0}", sale.TotalDue);
        }
    }
}
ReadOnly s_compQuery = CompiledQuery.Compile(Of AdventureWorksEntities, MyParams, IQueryable(Of SalesOrderHeader))( _
                Function(ctx, mySearchParams) _
                    From sale In ctx.SalesOrderHeaders _
                    Where sale.ShipDate > mySearchParams.startDate _
                       And sale.ShipDate < mySearchParams.endDate _
                       And sale.TotalDue > mySearchParams.totalDue _
                    Select sale)

Sub CompiledQuery7()

    Using context As New AdventureWorksEntities()

        Dim myParams As MyParams = New MyParams()
        myParams.startDate = New DateTime(2003, 3, 3)
        myParams.endDate = New DateTime(2003, 3, 8)
        myParams.totalDue = 700.0

        Dim sales = s_compQuery.Invoke(context, myParams)

        For Each sale In sales
            Console.WriteLine("ID: {0}", sale.SalesOrderID)
            Console.WriteLine("Ship date: {0}", sale.ShipDate)
            Console.WriteLine("Total due: {0}", sale.TotalDue)
        Next

    End Using
End Sub

Die Struktur, die die Abfrageparameter definiert:

struct MyParams
{
    public DateTime startDate;
    public DateTime endDate;
    public decimal totalDue;
}
Public Structure MyParams
    Public startDate As DateTime
    Public endDate As DateTime
    Public totalDue As Decimal
End Structure

Siehe auch