O Entity Framework Code First oferece suporte a procedimentos armazenados?

112

Assisti a várias apresentações do EF Code First e não vi como o EFCF funciona com stored procedures.

Como posso declarar um método que usará algum sp? Posso passar uma entidade para um método que chama sp sem mapear manualmente as propriedades da entidade para os parâmetros sp?

Além disso, o que acontece se eu mudar meu modelo? Isso deixaria cair meu sp enquanto recria a tabela do modelo? E quanto aos gatilhos?

Se essas coisas não forem suportadas, existem planos para apoiá-las no futuro?

Frennky
fonte
5
O roteiro da EF aponta que o EF 6 oferecerá suporte a procedimentos armazenados e funções para Code First. entityframework.codeplex.com/wikipage?title=Roadmap
frennky
Veja também: stackoverflow.com/questions/4873607/…
Nathan Koop

Respostas:

66

EDIT: Minha resposta original para EF4.1 (abaixo) agora está desatualizada. Veja a resposta abaixo de Diego Vega (que trabalha na equipe EF na Microsoft)!


@gsharp e Shawn Mclean: De onde você está conseguindo essas informações? Você ainda não tem acesso ao ObjectContext subjacente?

IEnumerable<Customer> customers = 
    ((IObjectContextAdapter)this)
    .ObjectContext.ExecuteStoreQuery<Customer>("select * from customers");

Substitua a instrução "select" por um procedimento armazenado e pronto.

Quanto à sua outra pergunta: Sim, infelizmente seus sp's serão derrotados. Você pode precisar adicionar as instruções "CREATE PROCEDURE" em seu código.

Para EF 4.2:

var customers = context.Database.SqlQuery<Customer>("select * from customers")
anon
fonte
Obrigado. Você poderia me indicar alguns links que tenham mais informações sobre o assunto.
frennky
1
Você desejará pesquisar as três funções Execute no objeto ObjectContext (ExecuteStoreQuery, ExecuteFunction e ExecuteStoreCommand).
anon
Eu entendi mal a pergunta. Eu estava pensando que ele queria criar uma base de código primeiro do SP.
gsharp
Você pode substituir Context.OnModelCreating e adicionar lógica personalizada para criar itens de banco de dados como procs armazenados por meio de código com bastante facilidade. Não é o ideal, mas em um aperto ele resolverá o problema.
Rick Strahl
Você não precisa do elenco IObjectContextAdapter. O DbContext pode manipular sp's ou instruções SQL personalizadas usando o objeto Database incorporado: context.Database.SqlQuery <Dummy> ("sp_GetDummy");
Steven K.
50

Update: De EF6 em diante, EF Code First oferece suporte a mapeamento de procedimento armazenado para inserções, atualizações e exclusões. Você pode especificar o mapeamento de procedimento armazenado durante a criação do modelo usando o método MapToStoredProcedures. Também oferecemos suporte para a criação automática de procedimentos armazenados básicos para essas operações. Veja a especificação do recurso aqui .

Resposta original: Não teremos suporte para mapear procedimentos armazenados no modelo em Code-First na primeira versão, nem teremos uma maneira de gerar procedimentos armazenados automaticamente para operações CRUD de seus tipos. Esses são recursos que gostaríamos de adicionar no futuro.

Como foi mencionado neste tópico, é possível voltar para ObjectContext, mas DbContext também fornece APIs interessantes para executar consultas e comandos SQL nativos (por exemplo, DbSet.SqlQuery, DbContext.Database.SqlQuery e DbContext.Database.ExecuteSqlCommand). As diferentes versões do SqlQuery têm a mesma funcionalidade básica de materialização que existe no EF4 (como ExecuteStoreQuery: http://msdn.microsoft.com/en-us/library/dd487208.aspx ).

Espero que isto ajude.

divega
fonte
6
BTW, escrevi uma postagem no blog há alguns dias que detalha como usar esses métodos para invocar procedimentos armazenados, até mesmo procedimentos armazenados com parâmetros de saída: blogs.msdn.com/b/diego/archive/2012/01/10/… .
divega de
3
No final de 2013, EF6 ainda está em desenvolvimento. Esperando três anos apenas para melhorar o suporte aos sprocs, suspiro.
DOK
1
@divega Existe suporte fortemente tipado para apenas selecionar valores de um procedimento armazenado - essa abordagem de código parece específica para gerenciar o tempo de vida do objeto? Especificamente, para pesquisas complexas, usando um procedimento armazenado spFooSearch com um parâmetro de saída TotalRows.
John Zabroski
31
    public IList<Product> GetProductsByCategoryId(int categoryId)
    {
        IList<Product> products;

        using (var context = new NorthwindData())
        {
            SqlParameter categoryParam = new SqlParameter("@categoryID", categoryId);
            products = context.Database.SqlQuery<Product>("Products_GetByCategoryID @categoryID", categoryParam).ToList();
        }

        return products;
    }

    public Product GetProductById(int productId)
    {
        Product product = null;

        using (var context = new NorthwindData())
        {
            SqlParameter idParameter = new SqlParameter("@productId", productId);
            product = context.Database.SqlQuery<Product>("Product_GetByID @productId", idParameter).FirstOrDefault();
        }

        return product;
    }
Marca
fonte
8

Uma solução mais segura de tipo seria esta:

http://strugglesofacoder.blogspot.be/2012/03/calling-stored-procedure-with-entity.html

O uso desta classe é:

var testProcedureStoredProcedure = new TestProcedureStoredProcedure() { Iets = 5, NogIets = true };

var result = DbContext.Database.ExecuteStoredProcedure(testProcedureStoredProcedure);
Luc Bos
fonte
O link não está mais ativo, mas aqui está o arquivo: web.archive.org/web/20150430090848/http://www.lucbos.net/2012/…
Arturo Torres Sánchez
2

Para .NET Core (EntityFrameworkCore), consegui colocá-los em funcionamento.

Pode não ser o mais legal, mas definitivamente funciona.

A migração para adicionar o procedimento armazenado tem a seguinte aparência :

using Microsoft.EntityFrameworkCore.Migrations;
using System.Text;

namespace EFGetStarted.AspNetCore.NewDb.Migrations
{
    public partial class StoredProcedureTest : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("CREATE PROCEDURE GetBlogForAuthorName");
            sb.AppendLine("@authorSearch varchar(100)");
            sb.AppendLine("AS");
            sb.AppendLine("BEGIN");
            sb.AppendLine("-- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.");
            sb.AppendLine("SET NOCOUNT ON;");
            sb.AppendLine("SELECT  Distinct Blogs.BlogId, Blogs.Url");
            sb.AppendLine("FROM Blogs INNER JOIN");
            sb.AppendLine("Posts ON Blogs.BlogId = Posts.BlogId INNER JOIN");
            sb.AppendLine("PostsAuthors ON Posts.PostId = PostsAuthors.PostId Inner JOIN");
            sb.AppendLine("Authors on PostsAuthors.AuthorId = Authors.AuthorId");
            sb.AppendLine("Where Authors.[Name] like '%' + @authorSearch + '%'");
            sb.AppendLine("END");

            migrationBuilder.Sql(sb.ToString());
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql("DROP PROCEDURE GetBlogForAuthorName");
        }
    }
}

Eu poderia então chamá-lo com o seguinte código:

var blogs = _context.Blogs.FromSql("exec GetBlogForAuthorName @p0", "rod").Distinct();

Mais tarde, tentei obter alguns dos dados relacionados (um para muitos dados de relacionamento, por exemplo, conteúdo do Post) e o blog voltou com o conteúdo do Post preenchido conforme solicitado.

HockeyJ
fonte