Usuário com melhor resposta
Como inserir dados usando Entity Framework em relacionamentos N.N

Pergunta
-
Olá galera, bom estou com uma duvída no seguinte:
Estou iniciando o uso do Entity Framework, segui essas video-aulas excelentes sobre desenvolvimento em N camadas com o Entity http://grou.ps/insidedotnet/videos do Andre Baltieri. Nele ele mostra tudo do CRUD basico, porem com somente uma entidade. Então criei um projeto e um banco de estudo com um relacionamento N para N entre a tabela Salarios e a Tabela Cargos.
Eu cadastro primeiramente o cargo e depois posso cadastrar vários salarios para ele assim como vários salarios podem estar em um mesmo cargo (eu sei não é o melhor exemplo, mas é so pra treino mesmo).
Com relacionamentos 1.1 ou 1.N consegui fazer sem maiores problemas. Porem nesse caso, toda a vez que eu vou inserir um salario ja selecionando o cargo do mesmo, ao invés dele cadastrar um novo registro criando uma nova entidade de salario e pegando a entidade de cargo que eu selecionei, ele cadastra novos dos dois. Ou Seja, eu seleciono um cargo, ele cria um novo cargo com os mesmos dados mais um codigo novo e associa ao salario novo e não é isso que eu quero, quero cadastrar um salario associado a um cargo ja cadastrado.
como sugere o video separei minha aplicação em 3 Camadas (DAO, BO e View) o nome das classes são EntidadeBD para o DAO, EntidadeN para a camada de BO.
Criei o modelo do entity apartir do Banco (Database First).
Segue os codigos:
Codigo da classe generica que tem todos os metodos crud genericos
namespace EstudoEF.BD.Classes { public abstract class AbstractCrudBD<T>:IBaseBD<T> where T : class { Estudo2Entities banco = new Estudo2Entities(); public void adicionar(T eEntidade) { banco.AddObject(eEntidade.GetType().Name, eEntidade); } public void alterar(T eEntidade) { banco.ApplyCurrentValues<T>(eEntidade.GetType().Name, eEntidade); } public void excluir(T eEntidade) { banco.DeleteObject(eEntidade); } public void anexar(T eEntidade) { banco.AttachTo(eEntidade.GetType().Name, eEntidade); } public void desmontar(T eEntidade) { banco.Detach(eEntidade); } public IQueryable<T> pesquisarCodigo(System.Linq.Expressions.Expression<Func<T, bool>> where) { return banco.CreateObjectSet<T>().Where(where); } public IQueryable<T> pesquisarTodos() { return banco.CreateObjectSet<T>(); } public void saveChanges() { banco.SaveChanges(); } public void dispose() { banco.Dispose(); } } }
Codigo da classe SalarioBD que tem o metodo de adicionarSalario puxando o cargo que eu quero associar:
public class SalarioBD:AbstractCrudBD<Salario>, ISalario { Estudo2Entities banco = new Estudo2Entities(); public IQueryable pesquisarPorCargo(System.Linq.Expressions.Expression<Func<Salario, bool>> where) { return banco.CreateObjectSet<Salario>().Where(where); } public void adicionarSalario(Salario s) { CargoBD cargoBD = new CargoBD(); s.Cargos.Select(cargo => banco.Cargo.FirstOrDefault(r => r.Codigo == cargo.Codigo)); banco.Salario.AddObject(s); banco.SaveChanges(); } }
Codigo da Pagina Web, do evento ao clicar no botão cadastrar salario
protected void btnCadastrar_Click(object sender, EventArgs e) { SalarioN sN = new SalarioN(); Salario s = new Salario(); Cargo c = new Cargo(); CargoN cN = new CargoN(); int codigoCargo = Convert.ToInt32(ddlCargo.SelectedItem.Value); c = cN.pesquisarCodigo(r => r.Codigo == codigoCargo).First(); cN.desmontar(c); //Detach cN.dispose(); s.Valor = Convert.ToDecimal(txtValor.Text); s.Cargos.Add(c); sN.adicionarSalario(s); sN.saveChanges(); CarregarSalarios(); }
alguém sabe onde estou errando? ja fazem algumas horas que estou travado nisso...
Desde já agradeço
- Editado Felipe Augusto Candido Rocha sexta-feira, 8 de fevereiro de 2013 09:36
Respostas
-
Olá Felipe,
Este erro ocorre quando vc tenta attachar uma entidade a um contexto, quando esta mesma entidade já esta attachada a outra instância.
Provavelmente vc fez a consulta de seu objeto e não chamou o dispose de seu DataContext.
Esse modelo de uso de Repositório Genérico eu não gosto mto, pois perdemos a liberdade com algumas coisas...
Outra coisa q não gosto é o uso do Datacontext sem chamar o Dispose logo em seguida... Isso gera consume desnecessário de memória e problemas como este, que vc esta sofrendo...
http://ferhenriquef.com/2012/05/15/dbcontext-e-o-objectcontext-no-deixe-seus-dados-em-memria/
Fiz alguns ajustes na sua classe de repositório que podem melhorar seu consume de memória com o EF. O ideal é que sempre que criada uma instância do seu DataContext ela seja fechada e não fique mto tempo aberta em memória.
namespace EstudoEF.BD.Classes { public abstract class AbstractCrudBD<T>:IBaseBD<T> where T : class { public void adicionar(T eEntidade) { using (var banco = new Estudo2Entities()) { banco.AddObject(eEntidade.GetType().Name, eEntidade); } } public void alterar(T eEntidade) { using (var banco = new Estudo2Entities()) { banco.ApplyCurrentValues<T>(eEntidade.GetType().Name, eEntidade); } } public void excluir(T eEntidade) { using (var banco = new Estudo2Entities()) { banco.DeleteObject(eEntidade); } } public void anexar(T eEntidade) { using (var banco = new Estudo2Entities()) { banco.AttachTo(eEntidade.GetType().Name, eEntidade); } } public void desmontar(T eEntidade) { using (var banco = new Estudo2Entities()) { banco.Detach(eEntidade); } } public IQueryable<T> pesquisarCodigo(System.Linq.Expressions.Expression<Func<T, bool>> where) { using (var banco = new Estudo2Entities()) { return banco.CreateObjectSet<T>().Where(where).ToArray(); } } public IQueryable<T> pesquisarTodos() { using (var banco = new Estudo2Entities()) { return banco.CreateObjectSet<T>().ToArray(); } } public void saveChanges() { using (var banco = new Estudo2Entities()) { banco.SaveChanges(); } } } }
Procure por algum canto que vc não chamou o Dispose de sua classe de repositório.
[]s!
MSc. Fernando Henrique Inocêncio Borba Ferreira
Microsoft MVP - Visual C#
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique
Entity Framework - Brasil: https://www.facebook.com/EntityFrameworkBrasil- Marcado como Resposta Felipe Augusto Candido Rocha quarta-feira, 20 de fevereiro de 2013 17:55
Todas as Respostas
-
Olá Felipe,
Abaixo eu tenho um exemplo de gravação de dados com EF e relacionamento N:N.
No caso o relacionamento é Livros X Autores.
Veja este bloco:
public void Save(Model.Book book) { using (DataContext context = new DataContext()) { if (book.Id == 0) { context.Entry(book.Category).State = EntityState.Unchanged; foreach (var author in book.Authors) { context.Entry(author).State = EntityState.Unchanged; } context.Books.Add(book); } else { var bookToUpdate = context.Books.Include("Category").Include("Authors").Where(b => b.Id == book.Id).Single(); UpdateBookAuthors(book.Authors.Select(a => a.Id).ToArray(), bookToUpdate, context); bookToUpdate.Year = book.Year; bookToUpdate.Name = book.Name; bookToUpdate.CategoryId = book.Category.Id; context.Entry(bookToUpdate).State = EntityState.Modified; } context.SaveChanges(); } } private void UpdateBookAuthors(int[] selectedAuthors, Model.Book bookToUpdate, DataContext context) { if (selectedAuthors == null) { bookToUpdate.Authors = new Model.Authors(); return; } var selAuthors = new HashSet<int>(selectedAuthors); var bookAuthors = new HashSet<int>(bookToUpdate.Authors.Select(a => a.Id)); foreach (var author in context.Authors) { if (selAuthors.Contains(author.Id)) { if (!bookAuthors.Contains(author.Id)) { bookToUpdate.Authors.Add(author); context.Entry(author).State = EntityState.Modified; } else { context.Entry(author).State = EntityState.Unchanged; } } else { if (bookAuthors.Contains(author.Id)) { bookToUpdate.Authors.Remove(author); } } } }
A primeira parte grava os dados na base de dados marcando-os com o status Unchanged, depois para fazer a atualização dos registros eu utilizo um método chamado UpdateBookAuthors, que valida os IDs dos itens.
Veja se é útil.
Se quiser ver tudo funcionando, baixe este exemplo: http://code.msdn.microsoft.com/EF-Code-First-Design-1558305c
[]s!
MSc. Fernando Henrique Inocêncio Borba Ferreira
Microsoft MVP - Visual C#
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique
Entity Framework - Brasil: https://www.facebook.com/EntityFrameworkBrasil -
-
Olá Felipe,
Veja se isso ajuda:
public void adicionarSalario(Salario s) { CargoBD cargoBD = new CargoBD(); foreach (var cargo in s.Cargos) { banco.ObjectStateManager.ChangeObjectState(cargo, System.Data.EntityState.Unchanged); } banco.Salario.AddObject(s); banco.SaveChanges(); banco.dispose(); }
[]s!
MSc. Fernando Henrique Inocêncio Borba Ferreira
Microsoft MVP - Visual C#
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique
Entity Framework - Brasil: https://www.facebook.com/EntityFrameworkBrasil
- Editado Fernando Henrique Inocêncio Borba FerreiraMicrosoft employee, Moderator sábado, 9 de fevereiro de 2013 13:27
-
Olá Fernando, obrigado pela resposta novamente.
Tentei fazer essa alteração que vc sugeriu no adicionarSalario, porem quando ele entra dentro do foreach acontece a seguinte Exception:
InvalidOperatorException: "O ObjectStateManager não contém uma ObjectStateEntry com uma referência a um objeto de tipo 'EstudoEF.BD.Cargo'."
ele não tem uma referencia ao estado do objeto do tipo Cargo?
Como posso resolver isso?
E se não fosse pedir demais, o que é esse State do Objeto que esta sendo setado como Unchanged?
- Editado Felipe Augusto Candido Rocha sábado, 9 de fevereiro de 2013 14:12
-
Olá Felipe,
Tente assim: (usando o attach)
_DatabaseContext.Applications.Attach(newApp ); public void adicionarSalario(Salario s) { CargoBD cargoBD = new CargoBD(); foreach (var cargo in s.Cargos) { banco.Cargos.Attach(cargo); banco.ObjectStateManager.ChangeObjectState(cargo, System.Data.EntityState.Unchanged); } banco.Salario.AddObject(s); banco.SaveChanges(); banco.dispose(); }
O Unchanged indica que o objeto não sofreu modificações, o que indica que não é para atualizar nem para incluir a instância que esta sendo passada em associação instância de Salario.
O Salário quando incluido passado no método AddObject adquiri o status Added, indicando que a instância deve ser incluida no banco de dados.
Se as demais instâncias associadas não tiverem um status, então elas assumem o status da instância "pai" associada, no caso o Added da Instância de salário. Isso causa a duplicidade de registros no seu banco de dados.
Com o Unchanged isso muda, pois vc indica que essas instâncias não devem gerar atualizações no banco de dados, apenas a inclusão na tabela associativa.
Bom trabalho.
[]s!MSc. Fernando Henrique Inocêncio Borba Ferreira
Microsoft MVP - Visual C#
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique
Entity Framework - Brasil: https://www.facebook.com/EntityFrameworkBrasil -
Olá Fernando, obrigado pela explicação, procurei alguns artigos e entendi melhor o estado dos objetos das entidades.
Tentei adicionar o Attach mas agora recebi outra exception:
foreach (var cargo in s.Cargos) { banco.Cargo.Attach(cargo); banco.ObjectStateManager.ChangeObjectState(cargo, System.Data.EntityState.Unchanged); } InvalidOperationException "Um objeto de entidade não pode ser referenciado por várias instâncias de IEntityChangeTracker."
Como pode perceber, eu não tenho a classe Cargos com uma lista de Cargo e sim somente a classe Cargo gerada do Model.
Ta difícil rsrs
-
Olá Felipe,
Este erro ocorre quando vc tenta attachar uma entidade a um contexto, quando esta mesma entidade já esta attachada a outra instância.
Provavelmente vc fez a consulta de seu objeto e não chamou o dispose de seu DataContext.
Esse modelo de uso de Repositório Genérico eu não gosto mto, pois perdemos a liberdade com algumas coisas...
Outra coisa q não gosto é o uso do Datacontext sem chamar o Dispose logo em seguida... Isso gera consume desnecessário de memória e problemas como este, que vc esta sofrendo...
http://ferhenriquef.com/2012/05/15/dbcontext-e-o-objectcontext-no-deixe-seus-dados-em-memria/
Fiz alguns ajustes na sua classe de repositório que podem melhorar seu consume de memória com o EF. O ideal é que sempre que criada uma instância do seu DataContext ela seja fechada e não fique mto tempo aberta em memória.
namespace EstudoEF.BD.Classes { public abstract class AbstractCrudBD<T>:IBaseBD<T> where T : class { public void adicionar(T eEntidade) { using (var banco = new Estudo2Entities()) { banco.AddObject(eEntidade.GetType().Name, eEntidade); } } public void alterar(T eEntidade) { using (var banco = new Estudo2Entities()) { banco.ApplyCurrentValues<T>(eEntidade.GetType().Name, eEntidade); } } public void excluir(T eEntidade) { using (var banco = new Estudo2Entities()) { banco.DeleteObject(eEntidade); } } public void anexar(T eEntidade) { using (var banco = new Estudo2Entities()) { banco.AttachTo(eEntidade.GetType().Name, eEntidade); } } public void desmontar(T eEntidade) { using (var banco = new Estudo2Entities()) { banco.Detach(eEntidade); } } public IQueryable<T> pesquisarCodigo(System.Linq.Expressions.Expression<Func<T, bool>> where) { using (var banco = new Estudo2Entities()) { return banco.CreateObjectSet<T>().Where(where).ToArray(); } } public IQueryable<T> pesquisarTodos() { using (var banco = new Estudo2Entities()) { return banco.CreateObjectSet<T>().ToArray(); } } public void saveChanges() { using (var banco = new Estudo2Entities()) { banco.SaveChanges(); } } } }
Procure por algum canto que vc não chamou o Dispose de sua classe de repositório.
[]s!
MSc. Fernando Henrique Inocêncio Borba Ferreira
Microsoft MVP - Visual C#
while(alive){ this.WriteCode(); }
Blog: http://ferhenriquef.com/
Twitter: @ferhenrique
Entity Framework - Brasil: https://www.facebook.com/EntityFrameworkBrasil- Marcado como Resposta Felipe Augusto Candido Rocha quarta-feira, 20 de fevereiro de 2013 17:55
-
Obrigado mais uma vez Fernando.
Mas ainda sim não deu certo, resultado, como ainda estava na etapa de projeto e modelando o sistema desisti do Model do Entity e usei logo o Code First e o Power Tools pra gerar as classes POCO a partir do banco, fiz os metodos Crud em cada classe e deu certinho (segui alguns tutoriais seus rs).
Code first é bem melhor você tem muito mais controle sobre a aplicação.
Valeu.
- Editado Felipe Augusto Candido Rocha quarta-feira, 20 de fevereiro de 2013 17:58