Compartilhar via


Procedimentos armazenados com vários conjuntos de resultados

Às vezes, ao usar procedimentos armazenados, você precisará retornar mais de um conjunto de resultados. Esse cenário geralmente é usado para reduzir o número de viagens de ida e volta de banco de dados necessárias para compor uma única tela. Antes do EF5, o Entity Framework permitia que o procedimento armazenado fosse chamado, mas retornava apenas o primeiro conjunto de resultados para o código de chamada.

Este artigo apresentará duas maneiras para você acessar mais de um conjunto de resultados de um procedimento armazenado no Entity Framework. Uma que usa apenas código e funciona com o Code First e o Designer EF e uma que só funciona com o Designer EF. As ferramentas e o suporte à API para isso devem ser aprimorados em versões futuras do Entity Framework.

Modelar

Os exemplos neste artigo usam um modelo básico de blog e postagens em que um blog tem muitas postagens e uma postagem pertence a um único blog. Usaremos um procedimento armazenado no banco de dados que retorna todos os blogs e postagens, algo com esta aparência:

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

Como acessar vários conjuntos de resultados com código

Podemos executar o código de uso para emitir um comando SQL bruto para executar o nosso procedimento armazenado. A vantagem dessa abordagem é que ela funciona com o Code First e o EF Designer.

Para ter vários conjuntos de resultados funcionando, precisamos remover para a API ObjectContext usando a interface IObjectContextAdapter.

Depois de termos um ObjectContext, podemos usar o método Translate para traduzir os resultados de nosso procedimento armazenado em entidades que podem ser rastreadas e usadas no EF normalmente. O código de exemplo a seguir demonstra isso em ação.

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

O método Translate aceita o leitor que recebemos quando executamos o procedimento, um nome EntitySet e uma MergeOption. O nome EntitySet será igual à propriedade DbSet no seu contexto derivado. A enumeração MergeOption controla como os resultados são tratados se a mesma entidade já existe na memória.

Aqui, iteramos por meio da coleção de blogs antes de chamarmos NextResult. Isso é importante considerando o código acima, pois o primeiro conjunto de resultados deve ser consumido antes de passar para o próximo.

Depois que os dois métodos translate são chamados, as entidades Blog e Post são controladas pelo EF da mesma maneira que qualquer outra entidade e, portanto, podem ser modificadas ou excluídas e salvas normalmente.

Observação

O EF não leva mapeamentos em consideração quando cria entidade usando o método Translate. Ele simplesmente vai equiparar nomes de coluna no conjunto de resultados com nomes de propriedade em suas classes.

Observação

Se você tiver o carregamento lento habilitado, ao acessar a propriedade de postagens em uma das entidades de blog, o EF se conectará ao banco de dados para carregar de maneira lenta todas as postagens, mesmo que todas já tenham sido carregadas. Isso ocorre porque o EF não consegue saber se você carregou ou não todas as postagens ou se há mais no banco de dados. Se você quiser evitar isso, precisará desabilitar o carregamento lento.

Vários conjuntos de resultados configurados no EDMX

Observação

Você deve direcionar o .NET Framework 4.5 para poder configurar vários conjuntos de resultados no EDMX. Se você estiver direcionando o .NET 4.0, poderá usar o método baseado em código mostrado na seção anterior.

Se você estiver usando o EF Designer, também poderá modificar seu modelo para que ele saiba mais sobre os diferentes conjuntos de resultados que serão retornados. Uma coisa que você deve saber antes é que as ferramentas não têm reconhecimento do conjunto de resultados múltiplos. Portanto, você precisará editar manualmente o arquivo edmx. Editar o arquivo edmx dessa forma funciona, mas também interrompe a validação do modelo no VS. Portanto, se você validar seu modelo, sempre receberá erros.

  • Para fazer isso, você precisa adicionar o procedimento armazenado ao modelo como faria para uma única consulta de conjunto de resultados.

  • Depois de fazer isso, você precisará clicar com o botão direito do mouse no modelo e selecionar Abrir com.. e Xml

    Open As

Depois de abrir o modelo como XML, você precisará executar as seguintes etapas:

  • Localize o tipo complexo e a importação de função em seu modelo:
    <!-- 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>

 

  • Remova o tipo complexo
  • Atualize a importação de função para que ela seja mapeada para suas entidades. Em nosso caso, ela terá a seguinte aparência:
    <FunctionImport Name="GetAllBlogsAndPosts">
      <ReturnType EntitySet="Blogs" Type="Collection(BlogModel.Blog)" />
      <ReturnType EntitySet="Posts" Type="Collection(BlogModel.Post)" />
    </FunctionImport>

Isso informa ao modelo que o procedimento armazenado retornará duas coleções, uma de entradas de blog e uma das entradas de postagem.

  • Localize o elemento de mapeamento de função:
    <!-- 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>
  • Substitua o mapeamento de resultado por um para cada entidade que está sendo retornada, como o seguinte:
    <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>

Também é possível mapear os conjuntos de resultados para tipos complexos, como o criado por padrão. Para fazer isso, crie um tipo complexo, em vez de removê-los e use os tipos complexos em todos os lugares que você usou os nomes de entidade nos exemplos acima.

Depois que esses mapeamentos forem alterados, você poderá salvar o modelo e executar o seguinte código para usar o procedimento armazenado:

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

Observação

Se você editar manualmente o arquivo edmx do seu modelo, ele será substituído se você regenerar o modelo do banco de dados.

Resumo

Aqui mostramos dois métodos diferentes de acesso a vários conjuntos de resultados usando o Entity Framework. Ambos são igualmente válidos dependendo de sua situação e preferências, e você deve escolher o que for ideal para suas circunstâncias. Planeja-se que o suporte a vários conjuntos de resultados será aprimorado em versões futuras do Entity Framework e que a execução das etapas neste documento não será mais necessária.