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.