包含多个结果集的存储过程Stored Procedures with Multiple Result Sets

有时,在使用存储过程时,您需要返回多个结果集。 此方案通常用于减少构成单个屏幕所需的数据库往返次数。 在 EF5 之前,实体框架允许调用存储过程,但只将第一个结果集返回给调用代码。

本文将介绍两种方法,可用于从实体框架中的存储过程访问多个结果集。 一个只使用代码,并处理代码 first 和 EF 设计器,另一个仅适用于 EF 设计器的。 此方面的工具和 API 支持应在实体框架的未来版本中得以改善。

模型Model

本文中的示例使用的是基本的博客和文章模型,其中的博客包含多篇文章,张贴内容属于单个博客。 我们将使用数据库中的存储过程,该存储过程返回所有博客和帖子,如下所示:

  1. CREATE PROCEDURE [dbo].[GetAllBlogsAndPosts]
  2. AS
  3. SELECT * FROM dbo.Blogs
  4. SELECT * FROM dbo.Posts

用代码访问多个结果集Accessing Multiple Result Sets with Code

我们可以执行使用代码发出原始 SQL 命令,以执行存储过程。 此方法的优点是它适用于代码 first 和 EF 设计器。

为了获得多个结果集,我们需要使用 IObjectContextAdapter 接口删除 ObjectContext API。

获得 ObjectContext 后,我们可以使用转换方法将存储过程的结果转换为可在 EF 中跟踪和使用的实体。 下面的代码示例演示如何执行此操作。

  1. using (var db = new BloggingContext())
  2. {
  3. // If using Code First we need to make sure the model is built before we open the connection
  4. // This isn't required for models created with the EF Designer
  5. db.Database.Initialize(force: false);
  6. // Create a SQL command to execute the sproc
  7. var cmd = db.Database.Connection.CreateCommand();
  8. cmd.CommandText = "[dbo].[GetAllBlogsAndPosts]";
  9. try
  10. {
  11. db.Database.Connection.Open();
  12. // Run the sproc
  13. var reader = cmd.ExecuteReader();
  14. // Read Blogs from the first result set
  15. var blogs = ((IObjectContextAdapter)db)
  16. .ObjectContext
  17. .Translate<Blog>(reader, "Blogs", MergeOption.AppendOnly);
  18. foreach (var item in blogs)
  19. {
  20. Console.WriteLine(item.Name);
  21. }
  22. // Move to second result set and read Posts
  23. reader.NextResult();
  24. var posts = ((IObjectContextAdapter)db)
  25. .ObjectContext
  26. .Translate<Post>(reader, "Posts", MergeOption.AppendOnly);
  27. foreach (var item in posts)
  28. {
  29. Console.WriteLine(item.Title);
  30. }
  31. }
  32. finally
  33. {
  34. db.Database.Connection.Close();
  35. }
  36. }

转换方法接受我们在执行过程、EntitySet 名称和 MergeOption 时收到的读取器。 EntitySet 名称将与派生上下文上的 DbSet 属性相同。 MergeOption 枚举控制在内存中已存在相同的实体时如何处理结果。

在这里,我们将循环访问博客的集合,然后调用 NextResult,这对于上述代码非常重要,因为在移动到下一个结果集之前必须使用第一个结果集。

一旦调用这两个转换方法,EF 就会以与任何其他实体相同的方式跟踪博客和公告实体,因此可以修改或删除它们并将其保存为正常。

备注

当使用转换方法创建实体时,EF 不会考虑任何映射。 它只是将结果集中的列名称与类的属性名进行匹配。

备注

这种情况下,如果启用了延迟加载,则访问其中一个博客实体上的 “发布” 属性后,EF 将连接到数据库以延迟加载所有发布,即使我们已经加载了所有发布。 这是因为 EF 无法知道是否已加载所有发布或数据库中是否存在其他内容。 如果要避免这种情况,则需要禁用延迟加载。

在 EDMX 中配置的多个结果集Multiple Result Sets with Configured in EDMX

备注

必须针对 .NET Framework 4.5,才能在 EDMX 中配置多个结果集。 如果面向的是 .NET 4.0,则可以使用上一节中所示的基于代码的方法。

如果使用的是 EF 设计器,则还可以修改模型,使其了解将返回的不同结果集。 在手头之前需要了解的一点是,该工具不是多个结果集感知,因此您需要手动编辑该 edmx 文件。 编辑此类 edmx 文件的操作将起作用,但它也会破坏 VS 中模型的验证。 如果验证模型,将始终会出现错误。

  • 要执行此操作,您需要将该存储过程添加到您的模型中,就像对单个结果集查询执行此操作一样。

  • 完成后,需要右键单击模型,然后选择 “打开方式“。 then Xml

    打开为

将模型作为 XML 打开后,需要执行以下步骤:

  • 在模型中查找复杂类型和函数导入:
  1. <!-- CSDL content -->
  2. <edmx:ConceptualModels>
  3. ...
  4. <FunctionImport Name="GetAllBlogsAndPosts" ReturnType="Collection(BlogModel.GetAllBlogsAndPosts_Result)" />
  5. ...
  6. <ComplexType Name="GetAllBlogsAndPosts_Result">
  7. <Property Type="Int32" Name="BlogId" Nullable="false" />
  8. <Property Type="String" Name="Name" Nullable="false" MaxLength="255" />
  9. <Property Type="String" Name="Description" Nullable="true" />
  10. </ComplexType>
  11. ...
  12. </edmx:ConceptualModels>
  • 删除复杂类型
  • 更新函数 import,使其映射到你的实体,在本例中,它将如下所示:
  1. <FunctionImport Name="GetAllBlogsAndPosts">
  2. <ReturnType EntitySet="Blogs" Type="Collection(BlogModel.Blog)" />
  3. <ReturnType EntitySet="Posts" Type="Collection(BlogModel.Post)" />
  4. </FunctionImport>

这会告知模型,存储过程将返回两个集合:一个博客项和一个发布项。

  • 查找函数映射元素:
  1. <!-- C-S mapping content -->
  2. <edmx:Mappings>
  3. ...
  4. <FunctionImportMapping FunctionImportName="GetAllBlogsAndPosts" FunctionName="BlogModel.Store.GetAllBlogsAndPosts">
  5. <ResultMapping>
  6. <ComplexTypeMapping TypeName="BlogModel.GetAllBlogsAndPosts_Result">
  7. <ScalarProperty Name="BlogId" ColumnName="BlogId" />
  8. <ScalarProperty Name="Name" ColumnName="Name" />
  9. <ScalarProperty Name="Description" ColumnName="Description" />
  10. </ComplexTypeMapping>
  11. </ResultMapping>
  12. </FunctionImportMapping>
  13. ...
  14. </edmx:Mappings>
  • 将结果映射替换为每个要返回的实体,如下所示:
  1. <ResultMapping>
  2. <EntityTypeMapping TypeName ="BlogModel.Blog">
  3. <ScalarProperty Name="BlogId" ColumnName="BlogId" />
  4. <ScalarProperty Name="Name" ColumnName="Name" />
  5. <ScalarProperty Name="Description" ColumnName="Description" />
  6. </EntityTypeMapping>
  7. </ResultMapping>
  8. <ResultMapping>
  9. <EntityTypeMapping TypeName="BlogModel.Post">
  10. <ScalarProperty Name="BlogId" ColumnName="BlogId" />
  11. <ScalarProperty Name="PostId" ColumnName="PostId"/>
  12. <ScalarProperty Name="Title" ColumnName="Title" />
  13. <ScalarProperty Name="Text" ColumnName="Text" />
  14. </EntityTypeMapping>
  15. </ResultMapping>

还可以将结果集映射到复杂类型,如默认创建的类型。 为此,您创建了一个新的复杂类型,而不是删除它们,并使用在上述示例中使用了实体名称的任何位置的复杂类型。

更改这些映射后,你可以保存模型并执行以下代码以使用该存储过程:

  1. using (var db = new BlogEntities())
  2. {
  3. var results = db.GetAllBlogsAndPosts();
  4. foreach (var result in results)
  5. {
  6. Console.WriteLine("Blog: " + result.Name);
  7. }
  8. var posts = results.GetNextResult<Post>();
  9. foreach (var result in posts)
  10. {
  11. Console.WriteLine("Post: " + result.Title);
  12. }
  13. Console.ReadLine();
  14. }

备注

如果手动编辑模型的 edmx 文件,则从数据库重新生成模型时,它将被覆盖。

摘要Summary

这里,我们介绍了两种不同的方法来使用实体框架访问多个结果集。 这两种方法都是相同的,具体取决于你的情况和首选项,你应选择最适合你的环境的方法。 它计划在未来版本的实体框架中对多个结果集的支持进行改进,并且不再需要执行本文档中的步骤。