none
Como inserir dados usando Entity Framework em relacionamentos N.N RRS feed

  • 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 



    sexta-feira, 8 de fevereiro de 2013 04:51

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

    domingo, 10 de fevereiro de 2013 13:27
    Moderador

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

    sexta-feira, 8 de fevereiro de 2013 18:59
    Moderador
  • Olá Fernando, obrigado pela resposta.

    Bom entendi parcialmente a sua implementação, o que seria aquele State da entidade? ela tem que ficar com estado Unchanged para pegar a entidade que já está persistida no banco e associar a nova é isso?

    sábado, 9 de fevereiro de 2013 03:32
  • 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


    sábado, 9 de fevereiro de 2013 13:27
    Moderador
  • 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?


    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

    sábado, 9 de fevereiro de 2013 14:21
    Moderador
  • 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

    sábado, 9 de fevereiro de 2013 14:39
  • 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

    domingo, 10 de fevereiro de 2013 13:27
    Moderador
  • 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.


    quarta-feira, 20 de fevereiro de 2013 17:58