Nas versões anteriores do modelo Code First era possível o uso de stored procedures apenas na execução de queries. Atualmente, no Entity Framework 6 (ainda como Release Candidate) é possível o mapeamento de stored procedures para execução de comandos de inclusão, alteração e exclusão de registros.
Instalação
Para adicionar as referências do Entity Framework 6 (que estava na versão Release Candidate durante a escrita deste post), digite a seguinte linha de comando em seu package manager console:
Restrições
1 – O mapeamento de stored procedures pode ser feito apenas via Fluent API.
2 – Não se pode utilizar um mapeamento mesclado (exemplo, “o comando de exclusão mapeado com stored procedures e os comandos e inclusão e alteração gerados dinamicamente pelo Entity Framework”).
Exemplo
Para o exemplo de uso de comandos mapeados por stored procedures utilizaremos uma estrutura de classes bastante simples, com um relacionamento muitos-para-muitos entre as classes. A estrutura utilizada no exemplo pode ser vista abaixo:
O código necessário para criar essa estrutura é demonstrado a seguir.
public class Book { public int Id { get; set; } public string Title { get; set; } public int Year { get; set; } public ICollection<Author> Authors { get; set; } } public class Author { public int Id { get; set; } public string Name { get; set; } public DateTime DateBirth { get; set; } public ICollection<Book> Books { get; set; } }
Para mapear os comandos com stored procedures é apenas preciso indicar o nome das stored procedures, e se necessário, as propriedades com seus respectivos parâmetros.
modelBuilder.Entity<Author>() .MapToStoredProcedures(e => { e.Insert(c => c.HasName("Author_Insert")); e.Update(c => c.HasName("Author_Update")); e.Delete(c => c.HasName("Author_Delete")); });
O código necessário para execução das stored procedures é listado abaixo:
CREATE PROCEDURE Author_Insert @Name nvarchar(max), @DateBirth datetime AS BEGIN INSERT Authors(Name, DateBirth) VALUES (@Name, @DateBirth) SELECT scope_identity() AS Id END GO CREATE PROCEDURE Author_Update @Id int, @Name nvarchar(max), @DateBirth datetime AS BEGIN UPDATE Authors SET Name = @Name, DateBirth = @DateBirth WHERE (Id = @Id) END GO CREATE PROCEDURE Author_Delete @Id int AS BEGIN DELETE Authors WHERE (Id = @Id) END GO
Desta forma bastante simples mapeamos comandos do EF com stored procedures. Um cenário bastante avançado, é o cenário no qual somos obrigados a mapear relacionamentos muitos-para-muitos. Neste cenário podemos utilizar os recursos de mapeamento de comandos por stored procedures para customizarmos o comportamento da inserção de dados.
Para mapearmos comandos de relacionamentos muitos-para-muitos a lógica de uso do EF não muda. Precisamos mapear as stored procedures com as entidades e criá-las no banco de dados.
O mapeamento das stored procedures deve seguir a seguinte estrutura:
modelBuilder.Entity<Author>() .HasMany<Book>(b => b.Books) .WithMany(a => a.Authors) .MapToStoredProcedures(s => s.Insert(i => i.HasName("AuhtorBook_Insert") .LeftKeyParameter(a => a.Id, "AuthorId") .RightKeyParameter(b => b.Id, "BookId")) .Delete(d => d.HasName("AuhtorBook_Delete") .LeftKeyParameter(a => a.Id, "AuthorId") .RightKeyParameter(b => b.Id, "BookId")));
E o código das stored procedures pode seguir a seguinte lógica:
CREATE PROCEDURE AuhtorBook_Insert @AuthorId int, @BookId int AS BEGIN INSERT AuthorBooks(Author_Id, Book_Id) VALUES (@AuthorId, @BookId) END GO CREATE PROCEDURE AuhtorBook_Delete @AuthorId int, @BookId int AS BEGIN DELETE AuthorBooks WHERE ((Author_Id = @AuthorId) AND (Book_Id = @BookId)) END GO
Todo o código C# utilizado no exemplo pode ser conferido abaixo.
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; namespace ConsoleApplicationEFProc { class Program { static void Main(string[] args) { CreateIfNotExists(); var newAuthor = new Author(); newAuthor.Name = "Carl Sagan"; newAuthor.DateBirth = new DateTime(1934, 11, 9); Insert(newAuthor); Update(newAuthor); Delete(newAuthor); Book newBook = new Book(); newBook.Title = "Cosmos"; newBook.Year = 1980; newBook.Authors = new Collection<Author>(); newBook.Authors.Add(newAuthor); Insert(newBook); } static void CreateIfNotExists() { using (var context = new DataContext()) { context.Database.CreateIfNotExists(); } } static void Insert(Book book) { using (var context = new DataContext()) { context.Books.Add(book); context.SaveChanges(); } } static void Insert(Author author) { using (var context = new DataContext()) { context.Authors.Add(author); context.SaveChanges(); } } static void Update(Author author) { using (var context = new DataContext()) { context.Entry(author).State = EntityState.Modified; context.SaveChanges(); } } static void Delete(Author author) { using (var context = new DataContext()) { context.Entry(author).State = EntityState.Deleted; context.SaveChanges(); } } } public class Book { public int Id { get; set; } public string Title { get; set; } public int Year { get; set; } public ICollection<Author> Authors { get; set; } } public class Author { public int Id { get; set; } public string Name { get; set; } public DateTime DateBirth { get; set; } public ICollection<Book> Books { get; set; } } public class DataContext : DbContext { public DbSet<Author> Authors { get; set; } public DbSet<Book> Books { get; set; } public DataContext() : base(@"Connection string") { Database.SetInitializer( new DropCreateDatabaseIfModelChanges<DataContext>()); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Author>() .HasMany<Book>(b => b.Books) .WithMany(a => a.Authors) .MapToStoredProcedures(s => s.Insert(i => i.HasName("AuhtorBook_Insert") .LeftKeyParameter(a => a.Id, "AuthorId") .RightKeyParameter(b => b.Id, "BookId")) .Delete(d => d.HasName("AuhtorBook_Delete") .LeftKeyParameter(a => a.Id, "AuthorId") .RightKeyParameter(b => b.Id, "BookId"))); modelBuilder.Entity<Author>() .MapToStoredProcedures(e => { e.Insert(c => c.HasName("Author_Insert")); e.Update(c => c.HasName("Author_Update")); e.Delete(c => c.HasName("Author_Delete")); }); base.OnModelCreating(modelBuilder); } } }
Por
MSc. Fernando Henrique Inocêncio Borba Ferreira
Microsoft Most Valuable Professional – Visual C#
Referências:
http://blogs.msdn.com/b/adonet/archive/2013/08/21/ef6-release-candidate-available.aspx
http://www.nuget.org/packages/EntityFramework
http://channel9.msdn.com/Shows/Visual-Studio-Toolbox/Entity-Framework-5-and-6
Boa Fê!
O mapeamento de store procedures é um recurso bem requisitado por muitos desenvolvedores e você forneceu uma ótima explicação! Claro e direto ao ponto, show de bola!
Abraços.
Obrigado pelos comentários, Edu!
[]s!
Muito bom Fernando. Parabéns pelo artigo.
Estou iniciando os testes com o EF6 aqui e os seus posts como sempre dando uma força enorme.
Abraço.
Vlw Alexandre! Obrigado por postar. Esse tipo de comentário realmente é bastante motivador! Obrigado. []s!
Parabéns pelo post e obrigado por compartilhar essas informações. Aqui na empresa, sem dúvida, vai ser um ponto a mais a favor do EF, pois o pessoal está acostumado a usar procedures para as operações de CRUD.