Gespeicherte Prozeduren mit mehreren Resultsets

Wenn Sie gespeicherte Prozeduren verwenden, benötigen Sie manchmal mehr als ein Resultset. Dieses Szenario wird häufig verwendet, um die Anzahl der Datenbank-Roundtrips zu reduzieren, die zum Verfassen einer einzelnen Anzeige erforderlich sind. Vor EF5 erlaubte Entity Framework zwar den Aufruf der gespeicherten Prozedur, gab aber nur das erste Resultset an den aufrufenden Code zurück.

In diesem Artikel werden zwei Möglichkeiten zum Zugreifen auf mehrere Resultsets aus einer gespeicherten Prozedur im Entity Framework erläutert. Eine, die nur Code verwendet und sowohl mit Code zuerst als auch mit dem EF-Designer funktioniert, und eine, die nur mit dem EF-Designer funktioniert. Die Tool- und API-Unterstützung dafür sollte in zukünftigen Versionen von Entity Framework verbessert werden.

Modell

Die Beispiele in diesem Artikel verwenden ein einfaches Blog- und Beitragsmodell, bei dem ein Blog viele Beiträge enthält und ein Beitrag zu einem einzigen Blog gehört. Wir verwenden eine gespeicherte Prozedur in der Datenbank, die alle Blogs und Beiträge zurückgibt, etwa wie folgt:

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

Zugreifen auf mehrere Resultsets mit Code

Wir können Code verwenden, um einen rohen SQL-Befehl zur Ausführung unserer gespeicherten Prozedur zu erteilen. Der Vorteil dieses Ansatzes besteht darin, dass er sowohl mit Code First als auch mit dem EF-Designer funktioniert.

Um mehrere Resultsets abzurufen, müssen wir mithilfe der IObjectContextAdapter-Schnittstelle auf die ObjectContext-API ablegen.

Sobald wir über einen ObjectContext verfügen, können wir die Translate-Methode verwenden, um die Ergebnisse unserer gespeicherten Prozedur in Entitäten zu übersetzen, die in EF normal nachverfolgt und verwendet werden können. Das folgende Codebeispiel veranschaulicht dies in der Praxis.

    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();
        }
    }

Die Translate-Methode akzeptiert den Leser, den wir empfangen haben, wenn wir die Prozedur, einen EntitySet-Namen und eine MergeOption ausgeführt haben. Der EntitySet-Name entspricht der DbSet-Eigenschaft für den abgeleiteten Kontext. Die MergeOption-Enumeration steuert, wie Ergebnisse behandelt werden, wenn dieselbe Entität bereits im Arbeitsspeicher vorhanden ist.

Hier durchlaufen wir die Sammlung von Blogs, bevor wir NextResult aufrufen. Dies ist angesichts des obigen Codes wichtig, da das erste Resultset verbraucht werden muss, bevor wir zum nächsten Resultset übergehen.

Sobald die beiden Übersetzungsmethoden aufgerufen werden, werden die Blog- und Post-Entitäten von EF auf die gleiche Weise wie jede andere Entität nachverfolgt und können so geändert oder gelöscht und als normal gespeichert werden.

Hinweis

EF berücksichtigt beim Erstellen von Entitäten mit der Translate-Methode keine Zuordnung. Es entspricht einfach Spaltennamen im Resultset mit Eigenschaftsnamen für Ihre Klassen.

Hinweis

Wenn Sie Lazy Loading aktiviert haben und auf die Posts-Eigenschaft einer der Blog-Entitäten zugreifen, stellt EF eine Verbindung zur Datenbank her, um alle Posts zu laden, auch wenn wir sie bereits alle geladen haben. Dies liegt daran, dass EF nicht wissen kann, ob Sie alle Beiträge geladen haben oder ob mehr in der Datenbank vorhanden ist. Wenn Sie dies vermeiden möchten, müssen Sie das Laden deaktivieren.

Mehrere Resultsets mit Konfigurierung in EDMX

Hinweis

Sie müssen .NET Framework 4.5 als Ziel verwenden, um mehrere Resultsets in EDMX konfigurieren zu können. Wenn Sie auf .NET 4.0 abzielen, können Sie die im vorherigen Abschnitt gezeigte codebasierte Methode verwenden.

Wenn Sie den EF-Designer verwenden, können Sie ihr Modell auch so ändern, dass es über die verschiedenen Resultsets informiert ist, die zurückgegeben werden. Sie sollten allerdings im Vorfeld wissen, dass das Tool nicht in der Lage ist, mehrere Resultsets zu verarbeiten, so dass Sie die EDMX-Datei manuell bearbeiten müssen. Die Bearbeitung der EDMX-Datei auf diese Weise wird funktionieren, aber sie wird auch die Validierung des Modells in VS unterbrechen. Wenn Sie also Ihr Modell überprüfen, erhalten Sie immer Fehler.

  • Um dies zu tun, müssen Sie die gespeicherte Prozedur ihrem Modell wie bei einer einzelnen Resultset-Abfrage hinzufügen.

  • Nachdem Sie dies vorgenommen haben, müssen Sie mit der rechten Maustaste auf Ihr Modell klicken und dann Öffnen mit... und anschließend XML auswählen

    Open As

Nachdem Sie das Modell als XML geöffnet haben, müssen Sie die folgenden Schritte ausführen:

  • Suchen Sie den komplexen Typ und den Funktionsimport in Ihrem Modell:
    <!-- 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>

 

  • Entfernen des komplexen Typs
  • Aktualisieren Sie den Funktionsimport so, dass er Ihren Entitäten zugeordnet ist, in unserem Fall sieht er wie folgt aus:
    <FunctionImport Name="GetAllBlogsAndPosts">
      <ReturnType EntitySet="Blogs" Type="Collection(BlogModel.Blog)" />
      <ReturnType EntitySet="Posts" Type="Collection(BlogModel.Post)" />
    </FunctionImport>

Damit wird dem Modell mitgeteilt, dass die gespeicherte Prozedur zwei Sammlungen zurückgeben wird – eine mit Blogeinträgen und eine mit Beitragseinträgen.

  • Suchen Sie das Funktionszuordnungselement:
    <!-- 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>
  • Ersetzen Sie die Ergebniszuordnung durch eine für jede zurückgegebene Entität, z. B. die folgenden:
    <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>

Es ist auch möglich, die Resultsets komplexen Typen zuzuordnen, z. B. dem standardmäßig erstellten. Dazu erstellen Sie einen neuen komplexen Typ, anstatt sie zu entfernen, und verwenden die komplexen Typen überall dort, wo Sie in den obigen Beispielen die Entitätsnamen verwendet haben.

Nachdem diese Zuordnungen geändert wurden, können Sie das Modell speichern und den folgenden Code ausführen, um die gespeicherte Prozedur zu verwenden:

    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();
    }

Hinweis

Wenn Sie die EDMX-Datei für Ihr Modell manuell bearbeiten, wird sie überschrieben, wenn Sie das Modell jemals aus der Datenbank neu generieren.

Zusammenfassung

Hier haben wir zwei verschiedene Methoden für den Zugriff auf mehrere Resultsets mithilfe von Entity Framework gezeigt. Beide sind je nach Ihrer Situation und Ihren Vorlieben gleichermaßen gültig, und Sie sollten den für Ihre Umstände am besten geeigneten auswählen. Es ist geplant, dass die Unterstützung für mehrere Resultsets in zukünftigen Versionen von Entity Framework verbessert wird und dass die Schritte in diesem Dokument nicht mehr erforderlich sind.