具有多個結果集的預存程式

有時候使用預存程式時,您必須傳回一個以上的結果集。 此案例通常用於減少撰寫單一畫面所需的資料庫來回行程數目。 在 EF5 之前,Entity Framework 會允許呼叫預存程式,但只會將第一個結果集傳回給呼叫端程式碼。

本文將說明兩種方式,可用來從 Entity Framework 中的預存程式存取多個結果集。 一個只使用程式碼,並且先與程式碼搭配 EF 設計工具運作,另一個只與 EF Designer 搭配運作。 此工具和 API 支援應該在未來的 Entity Framework 版本中改善。

Model

本文中的範例使用基本的部落格和文章模型,其中部落格有許多文章,而文章屬於單一部落格。 我們將在資料庫中使用預存程式來傳回所有部落格和文章,如下所示:

    CREATE PROCEDURE [dbo].[GetAllBlogsAndPosts]
    AS
        SELECT * FROM dbo.Blogs
        SELECT * FROM dbo.Posts

使用程式碼存取多個結果集

我們可以使用程式碼發出原始 SQL 命令來執行預存程式。 這個方法的優點是,它會先與程式碼和 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 之前逐一查看部落格集合,這在上述程式碼中很重要,因為必須先取用第一個結果集,才能移至下一個結果集。

呼叫這兩個翻譯方法之後,EF 會以與任何其他實體相同的方式追蹤 Blog 和 Post 實體,以便修改或刪除並儲存為一般。

注意

EF 不會在使用 Translate 方法建立實體時考慮任何對應。 它只會比對結果集中的資料行名稱與類別上的屬性名稱。

注意

如果您已啟用延遲載入,請在其中一個部落格實體上存取文章屬性,則 EF 會連線到資料庫以延遲載入所有文章,即使我們已經全部載入。 這是因為 EF 無法知道您是否已載入所有文章,或資料庫中是否有更多文章。 如果您想要避免這種情況,則必須停用延遲載入。

在 EDMX 中設定的多個結果集

注意

您必須以 .NET Framework 4.5 為目標,才能在 EDMX 中設定多個結果集。 如果您要以 .NET 4.0 為目標,您可以使用上一節所示的程式碼型方法。

如果您使用 EF 設計工具,您也可以修改模型,讓它知道將傳回的不同結果集。 手前要知道的一件事是工具不是多個結果集感知,因此您必須手動編輯 edmx 檔案。 編輯這類 edmx 檔案會正常運作,但它也會中斷 VS 中模型的驗證。 因此,如果您驗證模型,您一律會收到錯誤。

  • 若要這樣做,您必須將預存程式新增至模型,就像單一結果集查詢一樣。

  • 完成此動作之後,您必須以滑鼠右鍵按一下模型,然後選取 [開啟 With]。然後 選取 [Xml]。

    Open As

將模型開啟為 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>

這會告訴模型,預存程式會傳回兩個集合,其中一個部落格專案和一個文章專案。

  • 尋找函式對應專案:
    <!-- 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 檔案,如果您曾經從資料庫重新產生模型,就會覆寫該檔案。

摘要

在這裡,我們示範了使用 Entity Framework 存取多個結果集的兩種不同的方法。 這兩者都同樣有效,視您的情況和喜好設定而定,您應該選擇最適合您的情況。 計畫在未來的 Entity Framework 版本中,將改善對多個結果集的支援,而且不再需要執行本檔中的步驟。