ユーザーによって作成された LINQ クエリは、コマンド ツリーに変換されます。コマンド ツリーは、Entity Framework と互換性のあるクエリの表現です。コマンド ツリーは、その後データ ソースに対して実行されます。クエリの実行時には、すべてのクエリ式 (つまりクエリの全コンポーネント) が評価されます。これには結果の具体化で使用される式も含まれます。
クエリ式が評価されるタイミングはさまざまです。LINQ クエリは、クエリ変数の作成時ではなく、常にクエリ変数の反復処理時に実行されます。これを遅延実行と呼びます。クエリを即時に実行することもできます。これは、クエリの結果をキャッシュする場合に有効です。このことについては、このトピックの後半で説明します。
LINQ to Entities クエリを実行すると、クエリ内の一部の式がサーバー上で実行され、別の一部の式がクライアント上でローカルに実行される場合があります。クライアント側での式の評価は、サーバー上でクエリが実行される前に行われます。式がクライアント上で評価される場合、クエリ内の式がその評価の結果に置き換えられた後、サーバー上でクエリが実行されます。クエリはデータ ソースに対して実行されるため、クライアントで指定された動作よりもデータ ソースの構成が優先されます。たとえば、Null 値の処理方法や、数値の有効桁数などはサーバーの設定によって異なります。クエリの実行中にサーバーに対してスローされた例外は、クライアントに直接渡されます。
遅延実行
一連の値を返すようにクエリを設計がされている場合でも、クエリ変数そのものはクエリ コマンドのみを保存します。クエリに即時実行を促すメソッドが含まれていなければ、foreach ループまたは For Each ループでクエリ変数を反復処理するまで、実際にはクエリは実行されません。foreach ループまたは For Each ループ内のクエリ変数を反復処理するたびに、サーバーに対してクエリが実行されます。これを遅延実行と呼びます。遅延実行では、複数のクエリを組み合わせたり、クエリを拡張したりすることができます。その場合、クエリが変更され新しい操作が追加され、その変更が最終的な実行時に反映されます。クエリ変数そのものにクエリ結果が格納されることはありません。これは、任意のタイミングでクエリを実行できるということを意味します。たとえば、別のアプリケーションによって継続的に更新されているデータベースがあるとします。ご使用のアプリケーションで、最新データを取得するクエリを 1 つ作成し、特定の間隔でそれを繰り返し実行すると、毎回異なる結果を取得することができます。
LINQ to Entities クエリは Entity Framework でコマンド ツリーに変換され、結果が反復処理されるときにデータ ソースに対して実行されます。この時点で、変換に失敗すると、クライアントに対して例外がスローされます。
即時実行
一連の値を生成するクエリの遅延実行とは対照的に、シングルトン値を返すクエリは直ちに実行されます。シングルトン クエリの例としては、Average、Count、First、Max があります。これらのシングルトン クエリは、結果を計算するためにはシーケンスを生成する必要があるため、直ちに実行されます。即時実行は強制することもできます。これはクエリの結果をキャッシュする場合などに便利です。クエリまたはクエリ変数で ToList メソッド、ToDictionary メソッド、または ToArray メソッドを呼び出すと、シングルトン値を生成しないクエリの即時実行を強制できます。次の例では、ToArray メソッドを使用して、シーケンスを配列として即時評価します。
Using AWEntities As New AdventureWorksEntities
Dim products As ObjectQuery(Of Product) = AWEntities.Product
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 AWEntities = new AdventureWorksEntities())
{
ObjectQuery<Product> products = AWEntities.Product;
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);
}
}
また、foreach ループまたは For Each ループをクエリ式の直後に配置して実行を強制することも、ToList または ToArray を呼び出すことにより、単一のコレクション オブジェクト内のすべてのデータをキャッシュすることにより、実行を強制することもできます。
ストア実行
一般的に、LINQ to Entities の式はサーバー上で評価されるため、式の動作がデータ ソースのセマンティクスではなく、共通言語ランタイム (CLR) セマンティクスに従っているとは限りません。ただし、これには式がクライアント上で実行された場合などの例外があります。これは、サーバーとクライアントが異なるタイム ゾーンに存在する場合など、予期しない結果をもたらすことがあります。
クエリ内の一部の式はクライアント上で実行される場合があります。通常、ほとんどのクエリ実行はサーバーで発生するものと見なされます。データ ソースにマップされたクエリ要素に対して実行されたメソッドの他に、クエリにはローカルで実行できる式が含まれる場合がよくあります。クエリ式のローカルでの実行では、クエリ実行または結果の作成で使用できる値が生成されます。
値のバインド、サブ式、終了からのサブクエリ、およびクエリ結果へのオブジェクトの具体化などの特定の操作は、常にクライアントで実行されます。その最終的な結果として、これらの要素 (パラメータ値など) は実行中には更新できなくなります。匿名型は、データ ソースでインラインで作成できますが、必ずしもそれを想定することはできません。インラインのグループはデータ ソースでも作成できますが、すべてのインスタンスでそれが可能であると見なすことはできません。通常、サーバー上で何を作成できるかについて想定しないことが賢明です。
このセクションでは、コードをクライアント上でローカルに実行するシナリオについて説明します。ローカルで実行される式の種類の詳細については、「LINQ to Entities クエリ内の式」を参照してください。
リテラルとパラメータ
次の例に示した orderID
変数などのローカル変数は、クライアントで評価されます。
Dim sales As ObjectQuery(Of SalesOrderHeader) = AWEntities.SalesOrderHeader
Dim orderID As Integer = 51987
Dim salesInfo = _
From s In sales _
Where s.SalesOrderID = orderID _
Select s
ObjectQuery<SalesOrderHeader> sales = AWEntities.SalesOrderHeader;
int orderID = 51987;
IQueryable<SalesOrderHeader> salesInfo =
from s in sales
where s.SalesOrderID == orderID
select s;
メソッド パラメータもクライアントで評価されます。下の MethodParameterExample
メソッドに渡される orderID
パラメータがその例です。
Function MethodParameterExample(ByVal orderID As Integer)
Using AWEntities As New AdventureWorksEntities()
Dim sales As ObjectQuery(Of SalesOrderHeader) = AWEntities.SalesOrderHeader
Dim salesInfo = _
From s In sales _
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 AWEntities = new AdventureWorksEntities())
{
ObjectQuery<SalesOrderHeader> sales = AWEntities.SalesOrderHeader;
IQueryable<SalesOrderHeader> salesInfo =
from s in sales
where s.SalesOrderID == orderID
select s;
foreach (SalesOrderHeader sale in salesInfo)
{
Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue);
}
}
}
クライアントでのリテラルのキャスト
null から CLR 型へのキャストは、次のようにクライアントで実行されます。
Dim contacts As ObjectQuery(Of Contact) = AWEntities.Contact
Dim query = _
From c In contacts _
Where c.EmailAddress = CType(Nothing, String) _
Select c
ObjectQuery<Contact> contacts = AWEntities.Contact;
IQueryable<Contact> query =
from c in contacts
where c.EmailAddress == (string)null
select c;
Null 値が許容される Decimal などの型へのキャストは、次のようにクライアントで実行されます。
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
From product In products _
Where product.Weight = CType(23.77, Decimal?) _
Select product
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<Product> query =
from product in products
where product.Weight == (decimal?)23.77
select product;
リテラルのコンストラクタ
EDM 型にマップできる新しい CLR 型は、次のようにクライアントで実行されます。
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
From product In products _
Where product.Weight = New Decimal(23.77) _
Select product
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<Product> query =
from product in products
where product.Weight == new decimal(23.77)
select product;
新しい配列もクライアントで実行されます。
ストアの例外
クエリ実行時に発生したストア エラーはすべてクライアントに渡され、マッピングも処理もされません。
ストアの構成
ストアでクエリが実行される場合、ストアの構成はすべてのクライアント動作をオーバーライドし、ストア セマンティクスはすべての演算および式で表現されます。これにより、Null 比較、GUID 順序付け、precise 以外のデータ型 (浮動小数点型や DateTime など) を含む演算の精度や正確性、および文字列型演算などの領域において、CLR とストアの実行間で違いが発生する可能性があります。クエリ結果を調べる場合にはこのことに留意することが重要です。
CLR および SQL Server 間での動作の違いの例を次に示します。
SQL Server は CLR とは異なる方法で GUID を順序付けます。
SQL Server で Decimal 型を扱う場合は、結果の精度でも差違が発生する場合もあります。これは、SQL Server の decimal 型の固定有効桁数の要件によります。たとえば、Decimal 値 0.0、0.0、および 1.0 の平均は、クライアント上のメモリでは 0.3333333333333333333333333333 ですが、ストアでは 0.333333 です (SQL Server の decimal 型の既定の有効桁数に基づきます)。
SQL Server では、一部の文字列比較演算も CLR とは異なった方法で処理されます。文字列比較の動作は、サーバー上の照合順序の設定によって異なります。
LINQ to Entities クエリに含まれる関数呼び出しまたはメソッド呼び出しは、Entity Framework の正規関数にマップされ、その後 Transact-SQL に変換され、SQL Server データベースで実行されます。マップされたこれらの関数が表す動作は、基本クラス ライブラリでの実装とは異なる場合もあります。たとえば、空の文字列をパラメータとして Contains, StartsWith メソッドおよび EndsWith メソッドを呼び出すと、CLR での実行では true が返されますが、SQL Server での実行では false が返されます。また、末尾の空白のみが異なる 2 つの文字列は SQL Server では同じと見なされますが、CLR では同じではないと見なされるため、EndsWith メソッドは、異なる結果を返す場合があります。この例を次に示します。
Using AWEntities As New AdventureWorksEntities()
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
From p In 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 AWEntities = new AdventureWorksEntities())
{
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<string> query = from p in 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 "));
}