具有多个结果集的存储过程
有时,在使用存储过程时,需要返回多个结果集。 此方案通常用于减少组成单个屏幕所需的数据库往返次数。 在 EF5 之前,实体框架允许调用存储过程,但仅将第一个结果集返回给调用代码。
本文将介绍两种方法,可用于在实体框架中从存储过程访问多个结果集。 一种方法仅使用代码,Code First 和 EF 设计器均适用;另一种方法仅适用于 EF 设计器。 针对此操作的工具和 API 支持应该会在实体框架的未来版本中得到改进。
型号
本文中的示例使用基本的 Blog 和 Posts 模型,其中一篇博客有多个帖子,而一个帖子属于一篇博客。 我们将使用数据库中的存储过程来返回所有博客和帖子,如下所示:
CREATE PROCEDURE [dbo].[GetAllBlogsAndPosts]
AS
SELECT * FROM dbo.Blogs
SELECT * FROM dbo.Posts
使用代码访问多个结果集
我们可以执行 use 代码来发出原始 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 之前循环访问博客集合,根据上面的代码,这一点很重要,因为在移动到下一个结果集之前必须使用第一个结果集。
调用这两个 translate 方法之后,EF 会以与任何其他实体相同的方式跟踪 Blog 和 Post 实体,因此可以正常修改或删除并保存这些实体。
注意
EF 在使用 Translate 方法创建实体时不考虑任何映射。 它会直接将结果集中的列名与类上的属性名称匹配。
注意
如果启用延迟加载,并访问其中一个 blog 实体上的 posts 属性,EF 将连接到数据库以延迟加载所有帖子,即使我们已经加载所有帖子也是如此。 这是因为 EF 不知道你是否已加载所有帖子或者数据库中是否还有更多帖子。 如果要避免这种情况,需要禁用延迟加载。
在 EDMX 中配置多个结果集
注意
必须面向 .NET Framework 4.5 才能在 EDMX 中配置多个结果集。 如果面向 .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>
模型从中得知,存储过程将返回两个集合,一个包含 blog 条目,一个包含 post 条目。
- 查找函数映射元素:
<!-- 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();
}
注意
如果手动编辑模型的 edmx 文件,一旦从数据库重新生成模型,该文件就会被覆盖。
总结
我们在这里展示了使用实体框架访问多个结果集的两种不同方法。 这两种方法同样有效,具体取决于你的情况和偏好。你应该选择最适合自身情况的方法。 根据计划,对多个结果集的支持将在未来的实体框架版本中得到改进,并且不再需要执行本文档中的步骤。