Ya existe un objeto con la misma clave en ObjectStateManager. ObjectStateManager no puede rastrear múltiples objetos con la misma clave

Usar EF5 con un Patrón de Repositorio genérico y un ninject para la inhibición de la dependencia y encontrar un problema cuando trato de actualizar una entidad a la base de datos utilizando procesos almacenados con mi edmx.

mi actualización en DbContextRepository.cs es:

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; } } 

Desde mi AddressService.cs que vuelve a mi repository, tengo:

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

Cuando golpea el Attach y EntityState.Modified vomita con el error:

Ya existe un objeto con la misma clave en ObjectStateManager. ObjectStateManager no puede rastrear múltiples objetos con la misma clave.

He revisado muchas de las sugerencias en stack y en Internet y no he encontrado nada que lo resuelva. Cualquier solución sería apreciada.

¡Gracias!

Editar : respuesta original utilizada Find lugar de Local.SingleOrDefault . Funcionó en combinación con el método @ Juan’s Save , pero podría causar consultas innecesarias a la base de datos y probablemente nunca se haya ejecutado (la ejecución de la parte else provocaría una excepción porque Find ya había consultado la base de datos y no había encontrado la entidad por lo que no podría ser actualizado). Gracias a @BenSwayne por encontrar el problema.

Debe verificar si una entidad con la misma clave ya está rastreada por el contexto y modificar esa entidad en lugar de adjuntar la actual:

 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 puede ver, el problema principal es que el método SingleOrDefault necesita saber la clave para encontrar la entidad. Puede crear una interfaz simple exponiendo la clave ( IEntity en mi ejemplo) e implementarla en todas las entidades que desee procesar de esta manera.

No quería contaminar las clases EF generadas automáticamente al agregar interfaces o atributos. así que esto es realmente un poco de algunas de las respuestas anteriores (así que el crédito va para Ladislav Mrnka). Esto proporcionó una solución simple para mí.

Agregué un func al método de actualización que encontró la clave entera de la entidad.

 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 } } } 

Luego, cuando llamas a tu código, puedes usar …

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

En realidad puedes recuperar el Id a través de la reflexión, mira el siguiente ejemplo:

  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 deberías hacerlo de esta manera:

** Tenga en cuenta que YourDb debe ser una clase derivada de 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 } } } 

}

Otra solución (basada en la respuesta de @ Sergey) podría 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 } } } 

Y luego lo llamarías así:

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

Sin reflexión y si no desea usar interfaces, puede usar delegates funcionales para encontrar una entidad en la base de datos. Aquí está la muestra actualizada de arriba.

 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 } } } 

Lo llamarías así:

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

Si configura su contexto en AsNoTracking (), esto detendrá el seguimiento aspmvc de los cambios en la entidad en la memoria (que es lo que quiere de todos modos en la web).

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

Le recomendaría que lea más sobre esto en http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web -solicitud

Desconectar la entidad encontrada (ver attachedEntity en la solución de Ladislav ) y volver a adjuntar la modificada me funcionó a la perfección.

El razonamiento detrás de esto es simple: si algo es inmutable, reemplácelo (como un todo, entidad) desde donde pertenece al deseado.

Aquí hay un ejemplo de cómo hacer esto:

 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); 

Por supuesto, uno puede fácilmente darse cuenta de que este fragmento es parte de un método genérico, de ahí el uso de T , que es un parámetro de plantilla, y Set() .

Esa respuesta anterior puede ser EF 4.1+. Para aquellos en 4.0, pruebe este método simple … realmente no probado, pero sí adjunté y guardo mis cambios.

  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(); } 

Puede haber olvidado instalar el objeto fBLL = new FornecedorBLL(); en algun lugar