work unit pattern patron mvc generic framework example c# entity-framework ninject repository-pattern entity-framework-5

unit - repository pattern c# mvc



Ya existe un objeto con la misma clave en ObjectStateManager. ObjectStateManager no puede rastrear mĂșltiples objetos con la misma clave (10)

@ serj-sagan deberías hacerlo de esta manera:

** Tenga en cuenta que YourDb debe ser una clase derivada de DbContext.

public abstract class YourRepoBase<T> where T : class { private YourDb _dbContext; private readonly DbSet<T> _dbset; public virtual void Update(T entity) { var entry = _dbContext.Entry<T>(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>(); 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 } } }

}

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<T>(entity); if (entry.State == EntityState.Detached) { _context.Set<T>().Attach(entity); entry.State = EntityState.Modified; } }

Desde mi AddressService.cs que vuelve a mi repositorio, 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 pila y en Internet y no he encontrado nada que lo resuelva. Cualquier solución sería apreciada.

¡Gracias!


Desconectar la entidad encontrada (ver attachedEntity en solución ) 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<T>(); 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<T>() .


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

var entry = _dbContext.Entry<T>(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>(); 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 } }


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<RiskInsight>("GRC9Entities.RiskInsights", item); } } _db.SaveChanges(); }


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<TEntity, int> getKey) { if (entity == null) { throw new ArgumentException("Cannot add a null entity."); } var entry = _context.Entry<T>(entity); if (entry.State == EntityState.Detached) { var set = _context.Set<T>(); 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);


Otra solución (basada en la respuesta de @ Sergey) podría ser:

private void Update<T>(T entity, Func<T, bool> predicate) where T : class { var entry = Context.Entry(entity); if (entry.State == EntityState.Detached) { var set = Context.Set<T>(); 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)


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



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

private void Update<T>(T entity, Func<ObservableCollection<T>, T> locatorMap) where T : class { var entry = Context.Entry(entity); if (entry.State == EntityState.Detached) { var set = Context.Set<T>(); 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))


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<T>(entity); if (entry.State == EntityState.Detached) { var set = _context.Set<T>(); 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.