Um object com a mesma chave já existe no ObjectStateManager. O ObjectStateManager não pode rastrear vários objects com a mesma chave

Usando o EF5 com um Padrão de Repositório genérico e ninject para injência de dependência e executando em um problema ao tentar atualizar uma entidade para o database utilizando procs armazenados com o meu edmx.

minha atualização em DbContextRepository.cs é:

public override void Update(T entity) { if (entity == null) throw new ArgumentException("Cannot add a null entity."); var entry = _context.Entry(entity); if (entry.State == EntityState.Detached) { _context.Set().Attach(entity); entry.State = EntityState.Modified; } } 

Do meu AddressService.cs que retorna ao meu repository eu tenho:

  public int Save(vw_address address) { if (address.address_pk == 0) { _repo.Insert(address); } else { _repo.Update(address); } _repo.SaveChanges(); return address.address_pk; } 

Quando ele atinge o Attach e EntityState.Modified ele vomita com o erro:

Um object com a mesma chave já existe no ObjectStateManager. O ObjectStateManager não pode rastrear vários objects com a mesma chave.

Examinei muitas das sugestões na pilha e na Internet e não encontrei nada que resolvesse isso. Qualquer trabalho em torno seria apreciado.

Obrigado!

    Edit : Resposta original usada Find vez de Local.SingleOrDefault . Ele funcionava em combinação com o método Save do Save , mas poderia causar consultas desnecessárias ao database e provavelmente parte nunca seria executada (executar a parte else causaria exceção porque o Find já consultou o database e não encontrou a entidade para que não pudesse ser Atualizada). Obrigado ao @BenSwayne por encontrar o problema.

    Você deve verificar se uma entidade com a mesma chave já está sendo rastreada pelo contexto e modificar essa entidade em vez de append a atual:

     public override void Update(T entity) where T : IEntity { if (entity == null) { throw new ArgumentException("Cannot add a null entity."); } var entry = _context.Entry(entity); if (entry.State == EntityState.Detached) { var set = _context.Set(); T attachedEntity = set.Local.SingleOrDefault(e => e.Id == entity.Id); // You need to have access to key if (attachedEntity != null) { var attachedEntry = _context.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity); } else { entry.State = EntityState.Modified; // This should attach entity } } } 

    Como você pode ver, o principal problema é que o método SingleOrDefault precisa conhecer a chave para encontrar a entidade. Você pode criar uma interface simples expondo a chave ( IEntity no meu exemplo) e implementá-la em todas as entidades que deseja processar dessa maneira.

    Eu não queria poluir minhas classs EF geradas automaticamente adicionando interfaces ou atributos. então isso é realmente um pouco de algumas das respostas acima (então o crédito vai para Ladislav Mrnka). Isso forneceu uma solução simples para mim.

    Eu adicionei um func ao método de atualização que encontrou a chave inteira da entidade.

     public void Update(TEntity entity, Func getKey) { if (entity == null) { throw new ArgumentException("Cannot add a null entity."); } var entry = _context.Entry(entity); if (entry.State == EntityState.Detached) { var set = _context.Set(); T attachedEntity = set.Find.(getKey(entity)); if (attachedEntity != null) { var attachedEntry = _context.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity); } else { entry.State = EntityState.Modified; // This should attach entity } } } 

    Então quando você chamar seu código, você pode usar ..

     repository.Update(entity, key => key.myId); 

    Você pode realmente recuperar o ID através da reflection, veja o exemplo abaixo:

      var entry = _dbContext.Entry(entity); // Retreive the Id through reflection var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity); if (entry.State == EntityState.Detached) { var set = _dbContext.Set(); T attachedEntity = set.Find(pkey); // access the key if (attachedEntity != null) { var attachedEntry = _dbContext.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity); } else { entry.State = EntityState.Modified; // attach the entity } } 

    @ serj-sagan você deve fazer desta forma:

    ** Note que o YourDb deve ser uma class derivada do DbContext.

     public abstract class YourRepoBase where T : class { private YourDb _dbContext; private readonly DbSet _dbset; public virtual void Update(T entity) { var entry = _dbContext.Entry(entity); // Retreive the Id through reflection var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity); if (entry.State == EntityState.Detached) { var set = _dbContext.Set(); T attachedEntity = set.Find(pkey); // access the key if (attachedEntity != null) { var attachedEntry = _dbContext.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity); } else { entry.State = EntityState.Modified; // attach the entity } } } 

    }

    Outra solução (baseada na resposta do @Sergey) poderia ser:

     private void Update(T entity, Func predicate) where T : class { var entry = Context.Entry(entity); if (entry.State == EntityState.Detached) { var set = Context.Set(); T attachedEntity = set.Local.SingleOrDefault(predicate); if (attachedEntity != null) { var attachedEntry = Context.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity); } else { entry.State = EntityState.Modified; // This should attach entity } } } 

    E então você chamaria assim:

     Update(EntitytoUpdate, key => key.Id == id) 

    Sem refletir e se você não quiser usar interfaces, poderá usar delegates funcionais para localizar uma entidade no database. Aqui está a amostra atualizada acima.

     private void Update(T entity, Func, T> locatorMap) where T : class { var entry = Context.Entry(entity); if (entry.State == EntityState.Detached) { var set = Context.Set(); T attachedEntity = locatorMap(set.Local); if (attachedEntity != null) { var attachedEntry = Context.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity); } else { entry.State = EntityState.Modified; // This should attach entity } } } 

    Você chamaria assim:

     Update(EntitytoUpdate, p => p.SingleOrDefault(a => a.Id == id)) 

    Se você definir seu contexto como AsNoTracking (), isso irá parar aspmvc rastreando as mudanças na entidade na memory (que é o que você quer de qualquer maneira na web).

     _dbContext.Products.AsNoTracking().Find(id); 

    Eu recomendo que você leia mais sobre isso em http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web -aplicação

    Desappend a entidade encontrada (ver anexadoEntity na solução de Ladislav ) e voltar a append o modificado funcionou muito bem para mim.

    O raciocínio por trás disso é simples: se algo é imutável, substitua-o (como um todo, entidade) de onde ele pertence ao desejado.

    Aqui está um exemplo de como fazer isso:

     var set = this.Set(); if (this.Entry(entity).State == EntityState.Detached) { var attached = set.Find(id); if (attached != null) { this.Entry(attached).State = EntityState.Detached; } this.Attach(entity); } set.Update(entity); 

    Claro, pode-se facilmente descobrir que esse trecho é parte de um método genérico, daí o uso de T , que é um parâmetro de modelo, e Set() .

    Essa resposta acima pode ser EF 4.1+. Para aqueles em 4.0, tente este método simples … não foi realmente testado, mas anexou e salvou minhas alterações.

      public void UpdateRiskInsight(RiskInsight item) { if (item == null) { throw new ArgumentException("Cannot add a null entity."); } if (item.RiskInsightID == Guid.Empty) { _db.RiskInsights.AddObject(item); } else { item.EntityKey = new System.Data.EntityKey("GRC9Entities.RiskInsights", "RiskInsightID", item.RiskInsightID); var entry = _db.GetObjectByKey(item.EntityKey) as RiskInsight; if (entry != null) { _db.ApplyCurrentValues("GRC9Entities.RiskInsights", item); } } _db.SaveChanges(); } 

    Você pode ter esquecido de instaçia o object fBLL = new FornecedorBLL(); em lugar algun