複数の結果セットがあるストアド プロシージャ
ストアド プロシージャを使用する場合、複数の結果セットを返す必要があることがあります。 このシナリオは、1 つの画面を作成するために必要なデータベース ラウンド トリップの数を減らすためによく使用されます。 EF5 より前の Entity Framework では、ストアド プロシージャを呼び出すことはできますが、呼び出し元のコードに返されるのは最初の結果セットだけでした。
この記事では、Entity Framework のストアド プロシージャから複数の結果セットにアクセスするために使用できる 2 つの方法について説明します。 1 つはコードだけを使用し、Code First と EF デザイナーの両方で動作します。もう 1 つは、EF デザイナーだけで動作します。 このためのツールと API のサポートは、Entity Framework の今後のバージョンで改善される予定です。
モデル
この記事の例では、基本的なブログと投稿モデルを使用しています。ブログには多数の投稿があり、投稿は 1 つのブログに属しています。 次のように、すべてのブログと投稿を返すストアド プロシージャをデータベースで使用します。
CREATE PROCEDURE [dbo].[GetAllBlogsAndPosts]
AS
SELECT * FROM dbo.Blogs
SELECT * FROM dbo.Posts
コードによる複数の結果セットへのアクセス
コードを使用して、ストアド プロシージャを実行する生の SQL コマンドを発行できます。 この方法の利点は、Code First と EF デザイナーの両方で動作することです。
複数の結果セットを機能させるには、IObjectContextAdapter インターフェイスを使用して、ObjectContext API にドロップする必要があります。
ObjectContext が得られたら、Translate メソッドを使用して、ストアド プロシージャの結果を、EF で通常どおりに追跡および使用できるエンティティに変換できます。 次のコード サンプルは、この動作を示しています。
using (var db = new BloggingContext())
{
// If using Code First we need to make sure the model is built before we open the connection
// This isn't required for models created with the EF Designer
db.Database.Initialize(force: false);
// Create a SQL command to execute the sproc
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[GetAllBlogsAndPosts]";
try
{
db.Database.Connection.Open();
// Run the sproc
var reader = cmd.ExecuteReader();
// Read Blogs from the first result set
var blogs = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Blog>(reader, "Blogs", MergeOption.AppendOnly);
foreach (var item in blogs)
{
Console.WriteLine(item.Name);
}
// Move to second result set and read Posts
reader.NextResult();
var posts = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Post>(reader, "Posts", MergeOption.AppendOnly);
foreach (var item in posts)
{
Console.WriteLine(item.Title);
}
}
finally
{
db.Database.Connection.Close();
}
}
Translate メソッドは、プロシージャの実行時に受け取った閲覧者、EntitySet 名、および MergeOption を受け取ります。 EntitySet 名は、派生コンテキストの DbSet プロパティと同じになります。 MergeOption 列挙型は、同じエンティティが既にメモリに存在する場合に、どのように結果を処理するかを制御します。
ここでは、NextResult を呼び出す前にブログのコレクションを反復処理しています。上のコードでこのことが重要なのは、最初の結果セットを使用してから次の結果セットに移る必要があるためです。
2 つの translate メソッドが呼び出された後、Blog および Post エンティティは、EF によって他のエンティティと同じ方法で追跡されるため、通常どおりに変更、削除、または保存できます。
Note
EF では、Translate メソッドを使用してエンティティを作成するときに、マッピングは考慮されません。 結果セットの列名と、クラスのプロパティ名をマッチさせるだけです。
Note
遅延読み込みが有効になっている場合、いずれかの blog エンティティの posts プロパティにアクセスすると、すべての投稿が既に読み込まれている場合でも、EF がデータベースに接続し、すべての投稿を遅延読み込みします。 これは、すべての投稿が読み込まれたかどうかや、データベースにまだ残っているかどうかを、EF が認識できないためです。 これを回避するには、遅延読み込みを無効にする必要があります。
EDMX で構成される複数の結果セット
Note
EDMX で複数の結果セットを構成できるようにするには、.NET Framework 4.5 をターゲットにする必要があります。 .NET 4.0 をターゲットにしている場合は、前のセクションで説明したコードベースの方法を使用できます。
EF デザイナーを使用している場合は、返されるさまざまな結果セットが認識されるようにモデルを変更することもできます。 あらかじめ知っておくべきことは、ツールが複数の結果セットに対応していないため、edmx ファイルを手動で編集する必要があることです。 このように edmx ファイルを編集すると、正しく機能しますが、VS でのモデルの検証が中断されます。 そのため、モデルを検証すると、常にエラーが発生します。
このようにするには、単一の結果セットのクエリの場合と同じように、ストアド プロシージャをモデルに追加する必要があります。
このようにした後で、モデルを右クリックし、[プログラムから開く]、[Xml] の順に選択します
モデルを XML として開いてから、次の手順を実行する必要があります。
- モデル内の複合型と関数インポートを見つけます。
<!-- CSDL content -->
<edmx:ConceptualModels>
...
<FunctionImport Name="GetAllBlogsAndPosts" ReturnType="Collection(BlogModel.GetAllBlogsAndPosts_Result)" />
...
<ComplexType Name="GetAllBlogsAndPosts_Result">
<Property Type="Int32" Name="BlogId" Nullable="false" />
<Property Type="String" Name="Name" Nullable="false" MaxLength="255" />
<Property Type="String" Name="Description" Nullable="true" />
</ComplexType>
...
</edmx:ConceptualModels>
- 複合型を削除します
- 関数インポートを更新して、エンティティにマップされるようにします。この例では、次のようになります。
<FunctionImport Name="GetAllBlogsAndPosts">
<ReturnType EntitySet="Blogs" Type="Collection(BlogModel.Blog)" />
<ReturnType EntitySet="Posts" Type="Collection(BlogModel.Post)" />
</FunctionImport>
これにより、ストアド プロシージャから 2 つのコレクション (ブログ エントリが 1 つと投稿エントリが 1 つ) が返されることがモデルに通知されます。
- 関数マッピング要素を探します。
<!-- C-S mapping content -->
<edmx:Mappings>
...
<FunctionImportMapping FunctionImportName="GetAllBlogsAndPosts" FunctionName="BlogModel.Store.GetAllBlogsAndPosts">
<ResultMapping>
<ComplexTypeMapping TypeName="BlogModel.GetAllBlogsAndPosts_Result">
<ScalarProperty Name="BlogId" ColumnName="BlogId" />
<ScalarProperty Name="Name" ColumnName="Name" />
<ScalarProperty Name="Description" ColumnName="Description" />
</ComplexTypeMapping>
</ResultMapping>
</FunctionImportMapping>
...
</edmx:Mappings>
- 次のように、結果のマッピングを、返される各エンティティのマッピングに置き換えます。
<ResultMapping>
<EntityTypeMapping TypeName ="BlogModel.Blog">
<ScalarProperty Name="BlogId" ColumnName="BlogId" />
<ScalarProperty Name="Name" ColumnName="Name" />
<ScalarProperty Name="Description" ColumnName="Description" />
</EntityTypeMapping>
</ResultMapping>
<ResultMapping>
<EntityTypeMapping TypeName="BlogModel.Post">
<ScalarProperty Name="BlogId" ColumnName="BlogId" />
<ScalarProperty Name="PostId" ColumnName="PostId"/>
<ScalarProperty Name="Title" ColumnName="Title" />
<ScalarProperty Name="Text" ColumnName="Text" />
</EntityTypeMapping>
</ResultMapping>
また、結果セットを、既定で作成されるような複合型にマップすることもできます。 そうするには、複合型を削除する代わりに新たに作成し、上の例でエンティティ名を使用していたすべての場所で複合型を使用します。
これらのマッピングが変更されたら、モデルを保存し、次のコードを実行してストアド ロシージャを使用できます。
using (var db = new BlogEntities())
{
var results = db.GetAllBlogsAndPosts();
foreach (var result in results)
{
Console.WriteLine("Blog: " + result.Name);
}
var posts = results.GetNextResult<Post>();
foreach (var result in posts)
{
Console.WriteLine("Post: " + result.Title);
}
Console.ReadLine();
}
Note
モデルの edmx ファイルを手動で編集した場合は、モデルをデータベースから再生成すると上書きされます。
まとめ
ここでは、Entity Framework を使用して複数の結果セットにアクセスするための 2 つの異なる方法について説明しました。 どちらも状況や好みに応じて同じように有効であり、状況に適している方を選択する必要があります。 Entity Framework の将来のバージョンでは、複数の結果セットのサポートが改善され、このドキュメントの手順を実行する必要はなくなる予定です。
.NET