コンパイル済みクエリ (LINQ to Entities)
似たような構造のクエリを Entity Framework で何度も実行するアプリケーションがある場合は、クエリを一度コンパイルし、異なるパラメーターを指定して複数回実行することで、パフォーマンスを改善できる場合がよくあります。 たとえば、アプリケーションで特定の市区町村に住む顧客をすべて取得する必要がある場合は、ユーザーが実行時にフォーム内で市区町村を指定します。 LINQ to Entities では、この目的のためにコンパイル済みクエリをサポートしています。
.NET Framework 4.5 以降では、LINQ クエリは自動的にキャッシュされます。 ただし、引き続きコンパイル済み LINQ クエリを使用して後続の実行でさらにコストを削減できます。コンパイル済みクエリは、自動的にキャッシュされる LINQ クエリよりも効率的である場合があります。 メモリ内コレクションへ Enumerable.Contains
演算子を追加する LINQ to Entities クエリは自動的にキャッシュされません。 また、コンパイル済み LINQ クエリのメモリ内コレクションをパラメーターで表すことは許可されていません。
CompiledQuery クラスは、クエリをコンパイルおよびキャッシュして、再利用できるようにします。 このクラスには、概念上、CompiledQuery の Compile
メソッドとその複数のオーバーロードが存在します。 Compile
メソッドを呼び出すと、コンパイル済みクエリを表す新しいデリゲートを作成できます。 Compile
およびパラメーター値が指定された ObjectContext メソッドは、なんらかの結果 (IQueryable<T> インスタンスなど) をもたらすデリゲートを返します。 クエリは、初回実行時に 1 回だけコンパイルされます。 コンパイル時にクエリに対して設定したマージ オプションは、後から変更できません。 一度クエリがコンパイルされると、プリミティブ型のパラメーターを指定することだけはできますが、生成された SQL を変更するクエリの部分を置き換えることはできません。 詳しくは、「EF マージ オプションとコンパイル済みクエリ」をご覧ください。
CompiledQuery の Compile
メソッドによってコンパイルされる LINQ to Entities クエリの式は、Func<T1,T2,T3,T4,TResult> などのジェネリック Func
デリゲートのいずれかによって表されます。 クエリ式は、最大で、ObjectContext
パラメーター、戻りパラメーター、および 16 個のクエリ パラメーターをカプセル化できます。 17 個以上のクエリ パラメーターが必要な場合は、クエリ パラメーターを表すプロパティを持つ構造体を作成できます。 次に、その構造体のプロパティを設定した後、それらのプロパティをクエリ式で使用できます。
例 1
次の例では、入力パラメーターとして Decimal を受け取り、合計支払額が $200.00 以上である一連の注文を返すクエリをコンパイルして呼び出します。
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
例 2
次の例では、ObjectQuery<T> のインスタンスを返すクエリをコンパイルして呼び出します。
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
例 3
次の例では、製品の定価の平均を Decimal 値として返すクエリをコンパイルして呼び出します。
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
例 4
次の例では、入力パラメーターとして String を受け取り、指定された文字列でメール アドレスが始まる Contact
を返すクエリをコンパイルして呼び出します。
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
例 5
次の例では、入力パラメーターとして DateTime および Decimal を受け取り、注文日が 2003 年 3 月 8 日より遅く合計支払額が $300.00 未満である一連の注文を返すクエリをコンパイルして呼び出します。
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
例 6
次の例では、入力パラメーターとして DateTime を受け取り、注文日が 2004 年 3 月 8 日より遅い一連の注文を返すクエリをコンパイルして呼び出します。 このクエリは、注文情報を一連の匿名型として返します。 匿名型はコンパイラによって推論されるので、型パラメーターを CompiledQuery の Compile
メソッドに指定することはできず、型はクエリ自体で定義されます。
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
例 7
次の例では、入力パラメーターとしてユーザー定義の構造体を受け取り、一連の注文を返すクエリをコンパイルして呼び出します。 この構造体では開始日、終了日、合計支払額のクエリ パラメーターを定義しており、クエリは、2003 年 3 月 3 日から 3 月 8 日までに出荷された、合計支払額が $700.00 を超える注文を返します。
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
クエリ パラメーターを定義する構造体を次に示します。
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