このトピックでは、ADO.NET Entity Framework のパフォーマンス特性について説明し、Entity Framework アプリケーションのパフォーマンス向上に役立ついくつかの考慮事項について説明します。
クエリ実行のステージ
Entity Framework でのクエリのパフォーマンスを理解するには、概念モデルに対してクエリが実行され、データがオブジェクトとして返されるときに発生する操作を理解すると役立ちます。 次の表では、この一連の操作について説明します。
| オペレーション | 相対コスト | 頻度 | コメント |
|---|---|---|---|
| メタデータの読み込み | 適度 | 各アプリケーション ドメインに 1 回。 | Entity Framework で使用されるモデルとマッピングのメタデータは、 MetadataWorkspaceに読み込まれます。 このメタデータはグローバルにキャッシュされ、同じアプリケーション ドメイン内の ObjectContext の他のインスタンスで使用できます。 |
| データベース接続を開く | 適度1 | 必要に応じて。 | データベースへのオープン接続は貴重なリソースを消費するため、Entity Framework は必要に応じてデータベース接続を開いて閉じます。 接続を明示的に開くこともできます。 詳細については、「 接続とトランザクションの管理」を参照してください。 |
| ビューの生成 | 高 | 各アプリケーション ドメインに 1 回。 (事前に生成できます)。 | Entity Framework では、概念モデルに対してクエリを実行したり、データ ソースに対する変更を保存したりする前に、データベースにアクセスするための一連のローカル クエリ ビューを生成する必要があります。 これらのビューを生成するコストが高いため、ビューを事前に生成し、デザイン時にプロジェクトに追加できます。 詳細については、「 方法: クエリのパフォーマンスを向上させるためにビューを事前に生成する」を参照してください。 |
| クエリの準備 | 中2 | 一意のクエリごとに 1 回。 | クエリ コマンドを作成し、モデルとマッピングのメタデータに基づいてコマンド ツリーを生成し、返されるデータの形状を定義するためのコストが含まれます。 Entity SQL クエリ コマンドと LINQ クエリの両方がキャッシュされるため、同じクエリの後で実行する時間が短くなります。 コンパイル済みの LINQ クエリを使用して、後で実行する場合にこのコストを削減できます。また、コンパイルされたクエリは、自動的にキャッシュされる LINQ クエリよりも効率的です。 詳細については、「 コンパイル済みクエリ (LINQ to Entities)」を参照してください。 LINQ クエリの実行に関する一般的な情報については、「 LINQ to Entities」を参照してください。
手記:Enumerable.Contains演算子をメモリ内コレクションに適用する LINQ to Entities クエリは、自動的にはキャッシュされません。 また、コンパイル済みの LINQ クエリでのメモリ内コレクションのパラメーター化は許可されません。 |
| クエリの実行 | 低2 | クエリごとに 1 回。 | ADO.NET データ プロバイダーを使用してデータ ソースに対してコマンドを実行するコスト。 ほとんどのデータ ソースではクエリ プランがキャッシュされるため、同じクエリの後で実行する時間がさらに短い場合があります。 |
| 型の読み込みと検証 | 低3 | ObjectContextインスタンスごとに 1 回。 | 型は、概念モデルで定義されている型に対して読み込まれ、検証されます。 |
| トラッキング | 低3 | クエリが返すオブジェクトごとに 1 回。 4 | クエリで NoTracking マージ オプションを使用する場合、このステージはパフォーマンスに影響しません。 クエリで AppendOnly、 PreserveChanges、またはマージ オプション OverwriteChanges 使用する場合、クエリ結果は ObjectStateManagerで追跡されます。 EntityKeyは、クエリが返す追跡対象オブジェクトごとに生成され、ObjectStateEntryにObjectStateManagerを作成するために使用されます。 ObjectStateEntryに対して既存のEntityKeyが見つかった場合は、既存のオブジェクトが返されます。 PreserveChangesまたは OverwriteChanges オプションを使用すると、オブジェクトは返される前に更新されます。 詳細については、「 ID 解決、状態管理、および変更の追跡」を参照してください。 |
| オブジェクトの具体化 | 中程度3 | クエリが返すオブジェクトごとに 1 回。 4 | 返された DbDataReader オブジェクトを読み取り、オブジェクトを作成し、 DbDataRecord クラスの各インスタンスの値に基づいてプロパティ値を設定するプロセス。 オブジェクトが既に ObjectContext に存在し、クエリで AppendOnly または PreserveChanges マージ オプションを使用している場合、このステージはパフォーマンスに影響しません。 詳細については、「 ID 解決、状態管理、および変更の追跡」を参照してください。 |
1 データ ソース プロバイダーが接続プールを実装する場合、接続を開くコストはプール全体に分散されます。 .NET Provider for SQL Server では、接続プールがサポートされています。
2 クエリの複雑さが増すにつれてコストが増加します。
3 合計コストは、クエリによって返されるオブジェクトの数に比例して増加します。
4 EntityClient クエリはオブジェクトではなく EntityDataReader を返すので、EntityClient クエリではこのオーバーヘッドは必要ありません。 詳細については、「 Entity Framework 用の EntityClient プロバイダー」を参照してください。
その他の考慮事項
Entity Framework アプリケーションのパフォーマンスに影響する可能性があるその他の考慮事項を次に示します。
クエリの実行
クエリはリソースを集中的に消費する可能性があるため、コード内のどの時点で、どのコンピューターでクエリが実行されているかを検討してください。
遅延実行と即時実行
ObjectQuery<T>または LINQ クエリを作成すると、クエリがすぐに実行されない場合があります。 クエリの実行は、 foreach (C#) または For Each (Visual Basic) 列挙中、または List<T> コレクションに格納するために割り当てられる場合など、結果が必要になるまで遅延されます。 クエリの実行は、ExecuteでObjectQuery<T> メソッドを呼び出したとき、または単一クエリを返す LINQ メソッド (FirstやAnyなど) を呼び出すとすぐに開始されます。 詳細については、「 オブジェクト クエリ と クエリ実行 (LINQ to Entities)」を参照してください。
LINQ クエリのクライアント側の実行
LINQ クエリの実行は、データ ソースをホストするコンピューターで行われますが、LINQ クエリの一部がクライアント コンピューターで評価される場合があります。 詳細については、「 クエリ実行 (LINQ to Entities)」の「ストア実行」セクションを参照してください。
クエリとマッピングの複雑さ
個々のクエリとエンティティ モデル内のマッピングの複雑さは、クエリのパフォーマンスに大きな影響を与えます。
マッピングの複雑さ
概念モデル内のエンティティとストレージ モデル内のテーブル間の単純な 1 対 1 のマッピングよりも複雑なモデルでは、一対一のマッピングを持つモデルよりも複雑なコマンドが生成されます。
クエリの複雑さ
データ ソースに対して実行されるコマンドまたは大量のデータを返すコマンドに多数の結合が必要なクエリは、次の方法でパフォーマンスに影響する可能性があります。
単純に見える概念モデルに対するクエリを実行すると、データ ソースに対してより複雑なクエリが実行される可能性があります。 これは、Entity Framework が概念モデルに対するクエリをデータ ソースに対する同等のクエリに変換するためです。 概念モデルの 1 つのエンティティ セットがデータ ソース内の複数のテーブルにマップされている場合、またはエンティティ間のリレーションシップが結合テーブルにマップされている場合、データ ソース クエリに対して実行されるクエリ コマンドで 1 つ以上の結合が必要になる場合があります。
注
ToTraceStringクラスまたはObjectQuery<T> クラスのEntityCommand メソッドを使用して、特定のクエリのデータ ソースに対して実行されるコマンドを表示します。 詳細については、「 方法: ストア コマンドを表示する」を参照してください。
入れ子になった Entity SQL クエリは、サーバー上に結合を作成し、多数の行を返すことができます。
プロジェクション句の入れ子になったクエリの例を次に示します。
SELECT c, (SELECT c, (SELECT c FROM AdventureWorksModel.Vendor AS c ) As Inner2 FROM AdventureWorksModel.JobCandidate AS c ) As Inner1 FROM AdventureWorksModel.EmployeeDepartmentHistory AS cさらに、このようなクエリにより、クエリ パイプラインは、入れ子になったクエリ間でオブジェクトの重複を含む単一のクエリを生成します。 このため、1 つの列が複数回複製される可能性があります。 SQL Server を含む一部のデータベースでは、TempDB テーブルが非常に大きくなり、サーバーのパフォーマンスが低下する可能性があります。 入れ子になったクエリを実行するときは注意が必要です。
大量のデータを返すクエリは、クライアントが結果セットのサイズに比例する方法でリソースを消費する操作を実行している場合、パフォーマンスが低下する可能性があります。 このような場合は、クエリによって返されるデータの量を制限することを検討する必要があります。 詳細については、「方法: クエリ結果をページングする」を参照してください。
Entity Framework によって自動的に生成されるコマンドは、データベース開発者によって明示的に記述された同様のコマンドよりも複雑な場合があります。 データ ソースに対して実行されるコマンドを明示的に制御する必要がある場合は、テーブル値関数またはストアド プロシージャへのマッピングを定義することを検討してください。
人間関係
最適なクエリ パフォーマンスを実現するには、エンティティ間のリレーションシップをエンティティ モデルの関連付けとデータ ソースの論理リレーションシップの両方として定義する必要があります。
クエリ パス
既定では、 ObjectQuery<T>を実行すると、関連オブジェクトは返されません (ただし、リレーションシップ自体を表すオブジェクトは返されません)。 関連するオブジェクトは、次の 3 つの方法のいずれかで読み込むことができます。
ObjectQuery<T>が実行される前にクエリ パスを設定します。
オブジェクトが公開するナビゲーション プロパティに対して
Loadメソッドを呼び出します。LazyLoadingEnabledの [ObjectContext] オプションを [
true] に設定します。 これは、 エンティティ データ モデル デザイナーを使用してオブジェクトレイヤー コードを生成するときに自動的に行われることに注意してください。 詳細については、「 生成されたコードの概要」を参照してください。
使用するオプションを検討するときは、データベースに対する要求の数と、1 つのクエリで返されるデータの量の間にトレードオフがあることに注意してください。 詳細については、「 関連オブジェクトの読み込み」を参照してください。
クエリ パスの使用
クエリ パスは、クエリが返すオブジェクトのグラフを定義します。 クエリ パスを定義する場合、パスが定義するすべてのオブジェクトを返すために必要なデータベースに対する要求は 1 つだけです。 クエリ パスを使用すると、一見単純なオブジェクト クエリのデータ ソースに対して複雑なコマンドが実行される可能性があります。 これは、1 つのクエリで関連オブジェクトを返すために 1 つ以上の結合が必要であるために発生します。 この複雑さは、継承を持つエンティティや多対多リレーションシップを含むパスなど、複雑なエンティティ モデルに対するクエリの方が大きくなります。
注
ToTraceStringメソッドを使用して、ObjectQuery<T>によって生成されるコマンドを確認します。 詳細については、「 方法: ストア コマンドを表示する」を参照してください。
クエリ パスに含まれる関連オブジェクトが多すぎる場合、またはオブジェクトに含まれる行データが多すぎる場合、データ ソースがクエリを完了できない可能性があります。 これは、データ ソースの機能を超える中間一時ストレージがクエリに必要な場合に発生します。 この場合、関連オブジェクトを明示的に読み込むことで、データ ソース クエリの複雑さを軽減できます。
関連オブジェクトを明示的に読み込む
関連するオブジェクトを明示的に読み込むには、LoadまたはEntityCollection<TEntity>を返すナビゲーション プロパティに対してEntityReference<TEntity> メソッドを呼び出します。 オブジェクトを明示的に読み込むには、 Load が呼び出されるたびにデータベースへのラウンドトリップが必要です。
注
Load ステートメント (Visual Basic でforeach) を使用する場合など、返されたオブジェクトのコレクションをループしながらFor Eachを呼び出す場合、データ ソース固有のプロバイダーは、1 つの接続で複数のアクティブな結果セットをサポートする必要があります。 SQL Server データベースの場合は、プロバイダー接続文字列に MultipleActiveResultSets = true の値を指定する必要があります。
エンティティにLoadPropertyまたはEntityCollection<TEntity>プロパティがない場合は、EntityReference<TEntity> メソッドを使用することもできます。 これは、POCO エンティティを使用している場合に便利です。
関連するオブジェクトを明示的に読み込むと結合の数が減り、冗長データの量が減りますが、 Load はデータベースへの接続を繰り返す必要があります。これは、多数のオブジェクトを明示的に読み込むときにコストがかかる場合があります。
変更の保存
SaveChangesで ObjectContext メソッドを呼び出すと、コンテキスト内の追加、更新、または削除されたオブジェクトごとに、個別の作成、更新、または削除コマンドが生成されます。 これらのコマンドは、1 つのトランザクション内のデータ ソースで実行されます。 クエリと同様に、作成、更新、および削除操作のパフォーマンスは、概念モデルでのマッピングの複雑さに依存します。
分散トランザクション
分散トランザクション コーディネーター (DTC) によって管理されるリソースを必要とする明示的なトランザクションの操作は、DTC を必要としない同様の操作よりもはるかにコストがかかります。 DTC への昇格は、次の状況で発生します。
明示的なトランザクションを常に DTC に昇格させる操作が含まれている SQL Server 2000 データベースまたはその他のデータ ソースに対する明示的なトランザクション。
接続が Entity Framework によって管理されている場合の SQL Server 2005 に対する操作を含む明示的なトランザクション。 これは、接続が閉じられ、Entity Framework の既定の動作である 1 つのトランザクション内で再度開くたびに、SQL Server 2005 が DTC に昇格するためです。 この DTC 昇格は、SQL Server 2008 を使用する場合には発生しません。 SQL Server 2005 を使用するときにこの昇格を回避するには、トランザクション内で接続を明示的に開いて閉じる必要があります。 詳細については、「 接続とトランザクションの管理」を参照してください。
明示的なトランザクションは、 System.Transactions トランザクション内で 1 つ以上の操作が実行されるときに使用されます。 詳細については、「 接続とトランザクションの管理」を参照してください。
パフォーマンス向上のための戦略
次の戦略を使用して、Entity Framework のクエリの全体的なパフォーマンスを向上させることができます。
ビューの事前生成
エンティティ モデルに基づくビューの生成は、アプリケーションが初めてクエリを実行する場合に大きなコストがかかります。 EdmGen.exe ユーティリティを使用して、デザイン時にプロジェクトに追加できる Visual Basic または C# コード ファイルとしてビューを事前に生成します。 また、テキスト テンプレート変換ツールキットを使用して、事前コンパイル済みのビューを生成することもできます。 事前に生成されたビューは、実行時に検証され、指定されたエンティティ モデルの現在のバージョンと一致していることを確認します。 詳細については、「 方法: クエリのパフォーマンスを向上させるためにビューを事前に生成する」を参照してください。
非常に大規模なモデルを使用する場合は、次の考慮事項が適用されます。
.NET メタデータ形式では、指定されたバイナリ内のユーザー文字列文字の数が 16,777,215 (0xFFFFFF) に制限されます。 非常に大きなモデルのビューを生成していて、ビュー ファイルがこのサイズ制限に達すると、"No logical space left to create more user strings." (ユーザー文字列を追加するために論理領域が残っていない) というエラーが発生します。コンパイル エラーが発生します。 このサイズ制限は、すべてのマネージド バイナリに適用されます。 詳細については、大規模で複雑なモデルを操作するときにエラーを回避する方法を示す ブログ を参照してください。
クエリに NoTracking マージ オプションを使用することを検討する
オブジェクト コンテキストで返されたオブジェクトを追跡するために必要なコストがあります。 オブジェクトに対する変更を検出し、同じ論理エンティティに対する複数の要求が同じオブジェクト インスタンスを返すようにするには、オブジェクトを ObjectContext インスタンスにアタッチする必要があります。 オブジェクトに対して更新または削除を行う予定がない場合、ID 管理を必要としない場合は、クエリの実行時に NoTracking マージ オプションを使用することを検討してください。
正しい量のデータを返す
一部のシナリオでは、 Include メソッドを使用してクエリ パスを指定すると、データベースへのラウンド トリップが少なくなるため、はるかに高速になります。 ただし、他のシナリオでは、結合の数が少ない単純なクエリの結果、データの冗長性が低下するため、関連オブジェクトを読み込むためのデータベースへのラウンド トリップが高速になる場合があります。 このため、関連オブジェクトを取得するさまざまな方法のパフォーマンスをテストすることをお勧めします。 詳細については、「 関連オブジェクトの読み込み」を参照してください。
1 つのクエリで返されるデータが多くなりすぎないようにするには、クエリの結果をより管理しやすいグループにページングすることを検討してください。 詳細については、「方法: クエリ結果をページングする」を参照してください。
ObjectContext のスコープを制限する
ほとんどの場合、ObjectContext ステートメント (Visual Basic ではusing) 内にUsing…End Using インスタンスを作成する必要があります。 これにより、コードがステートメント ブロックを終了したときに、オブジェクト コンテキストに関連付けられているリソースが自動的に破棄されるため、パフォーマンスが向上する可能性があります。 ただし、コントロールがオブジェクト コンテキストによって管理されるオブジェクトにバインドされている場合は、バインドが必要であり、手動で破棄されている限り、 ObjectContext インスタンスを維持する必要があります。 詳細については、「 接続とトランザクションの管理」を参照してください。
データベース接続を手動で開く方法を検討する
アプリケーションが一連のオブジェクト クエリを実行したり、データ ソースに対する作成、更新、削除の操作を保持するために SaveChanges を頻繁に呼び出す場合、Entity Framework はデータ ソースへの接続を継続的に開いて閉じる必要があります。 このような場合は、これらの操作の開始時に手動で接続を開き、操作が完了したら接続を閉じるか破棄することを検討してください。 詳細については、「 接続とトランザクションの管理」を参照してください。
パフォーマンス データ
Entity Framework のパフォーマンス データの一部は、 ADO.NET チームブログの次の投稿で公開されています。