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
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.