Procédures stockées avec plusieurs jeux de résultats

Parfois, lorsque vous utilisez des procédures stockées, vous devez retourner plusieurs jeux de résultats. Ce scénario est couramment utilisé pour réduire le nombre d’allers-retours de base de données requis pour composer un seul écran. Avant EF5, Entity Framework autoriserait l’appel de la procédure stockée, mais retournerait uniquement le premier jeu de résultats au code appelant.

Cet article vous montre deux façons d’accéder à plusieurs jeux de résultats à partir d’une procédure stockée dans Entity Framework. Une qui utilise uniquement du code et fonctionne avec Code First et EF Designer et une qui fonctionne uniquement avec EF Designer. La prise en charge des outils et de l’API devrait s’améliorer dans les futures versions d’Entity Framework.

Modèle

Les exemples de cet article utilisent un modèle basique de Blogs et de Publications, dans lequel un blog a plusieurs Publications et une publication appartient à un seul blog. Nous allons utiliser une procédure stockée dans la base de données qui retourne tous les blogs et publications, comme suit :

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

Accès à plusieurs jeux de résultats avec du code

Nous pouvons exécuter le code pour émettre une commande SQL brute et exécuter notre procédure stockée. L’avantage de cette approche est qu’elle fonctionne avec Code First et ED Designer.

Pour obtenir plusieurs jeux de résultats, nous devons passer à l’API ObjectContext à l’aide de l’interface IObjectContextAdapter.

Une fois que nous avons un ObjectContext, nous pouvons utiliser la méthode Translate pour traduire les résultats de notre procédure stockée en entités qui peuvent être suivies et utilisées dans EF comme normale. L'exemple de code suivant en fait la démonstration.

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

La méthode Translate accepte le lecteur que nous avons reçu lors de l’exécution de la procédure, d’un nom EntitySet et d’une MergeOption. Le nom EntitySet est identique à la propriété DbSet sur votre contexte dérivé. L’énumération MergeOption contrôle la façon dont les résultats sont gérés si la même entité existe déjà en mémoire.

Ici, nous parcourons la collection de blogs avant d’appeler NextResult, cela est important étant donné le code ci-dessus, car le premier jeu de résultats doit être consommé avant de passer au jeu de résultats suivant.

Une fois les deux méthodes de traduction appelées, les entités Blog et Publication sont suivies par EF de la même façon que n’importe quelle autre entité et peuvent ainsi être modifiées ou supprimées et enregistrées normalement.

Remarque

EF ne prend pas en compte le mappage lorsqu’il crée des entités à l’aide de la méthode Translate. Il correspond simplement aux noms de colonnes dans le jeu de résultats avec des noms de propriétés sur vos classes.

Remarque

Si vous avez activé le chargement différé, et que vous accédez à la propriété de publications sur l’une des entités de blog, EF se connecte à la base de données pour charger de manière différée toutes les publications, même si nous les avons déjà toutes chargées. En effet, EF ne peut pas savoir si vous avez chargé ou non toutes les publications ou s’il y en a plus dans la base de données. Si vous souhaitez éviter cela, vous devez désactiver le chargement différé.

Jeux de résultats multiples configurés dans EDMX

Remarque

Vous devez cibler .NET Framework 4.5 pour pouvoir configurer plusieurs jeux de résultats dans EDMX. Si vous ciblez .NET 4.0, vous pouvez utiliser la méthode basée sur le code indiquée dans la section précédente.

Si vous utilisez EF Designer, vous pouvez également modifier votre modèle afin qu’il sache les différents jeux de résultats qui seront retournés. Une chose à savoir à l’avance est que l’outil ne prend pas en compte de plusieurs jeux de résultats. Vous devez donc modifier manuellement le fichier edmx. La modification du fichier edmx comme celui-ci fonctionnera, mais elle interrompt également la validation du modèle dans VS. Par conséquent, si vous validez votre modèle, vous obtiendrez toujours des erreurs.

  • Pour ce faire, vous devez ajouter la procédure stockée à votre modèle comme vous le feriez pour une requête de jeu de résultats unique.

  • Une fois que vous l’avez, vous devez cliquer avec le bouton droit sur votre modèle, puis sélectionner Ouvrir avec... et Xml

    Open As

Une fois le modèle ouvert au format XML, vous devez effectuer les étapes suivantes :

  • Recherchez le type complexe et l’importation de fonction dans votre modèle :
    <!-- 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>

 

  • Supprimez le type complexe
  • Mettez à jour l’importation de la fonction pour qu’elle corresponde à vos entités, dans notre cas, elle se présente comme suit :
    <FunctionImport Name="GetAllBlogsAndPosts">
      <ReturnType EntitySet="Blogs" Type="Collection(BlogModel.Blog)" />
      <ReturnType EntitySet="Posts" Type="Collection(BlogModel.Post)" />
    </FunctionImport>

Cela indique au modèle que la procédure stockée retourne deux collections, l’une des entrées de blog et l’une des entrées de publication.

  • Recherchez l’élément de mappage de la fonction :
    <!-- 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>
  • Remplacez le mappage de résultats par un mappage pour chaque entité retournée, par exemple :
    <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>

Il est également possible de mapper les jeux de résultats à des types complexes, tels que celui créé par défaut. Pour ce faire, créez un type complexe, au lieu de les supprimer, puis utilisez les types complexes partout où vous aviez utilisé les noms d’entité dans les exemples ci-dessus.

Une fois ces mappages modifiés, vous pouvez enregistrer le modèle et exécuter le code suivant pour utiliser la procédure stockée :

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

Remarque

Si vous modifiez manuellement le fichier edmx pour votre modèle, il sera remplacé si vous régénérez le modèle à partir de la base de données.

Résumé

Nous avons montré deux méthodes différentes d’accès à plusieurs jeux de résultats à l’aide d’Entity Framework. Les deux sont également valides en fonction de votre situation et de vos préférences et vous devez choisir celle qui semble la mieux adaptée à votre cas. Il est prévu que la prise en charge de plusieurs jeux de résultats soit améliorée dans les futures versions d’Entity Framework et que l’exécution des étapes de ce document ne sera plus nécessaire.