c# - framework - ASP.NET MVC: no se pudo adjuntar una entidad del tipo ''MODELNAME'' porque otra entidad del mismo tipo ya tiene el mismo valor de clave primaria
entity framework tutorial español (14)
En pocas palabras, la excepción se produce durante el modelo de envoltura POSTing y cambiando el estado de una entrada a ''Modificado''. Antes de cambiar el estado, el estado se establece en ''Independiente'', pero llamar a Attach () arroja el mismo error. Estoy usando EF6.
Encuentra mi código a continuación (los nombres de los modelos han sido cambiados para que sea más fácil de leer)
Modelo
// Wrapper classes
public class AViewModel
{
public A a { get; set; }
public List<B> b { get; set; }
public C c { get; set; }
}
Controlador
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (!canUserAccessA(id.Value))
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
var aViewModel = new AViewModel();
aViewModel.A = db.As.Find(id);
if (aViewModel.Receipt == null)
{
return HttpNotFound();
}
aViewModel.b = db.Bs.Where(x => x.aID == id.Value).ToList();
aViewModel.Vendor = db.Cs.Where(x => x.cID == aViewModel.a.cID).FirstOrDefault();
return View(aViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(AViewModel aViewModel)
{
if (!canUserAccessA(aViewModel.a.aID) || aViewModel.a.UserID != WebSecurity.GetUserId(User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
if (ModelState.IsValid)
{
db.Entry(aViewModel.a).State = EntityState.Modified; //THIS IS WHERE THE ERROR IS BEING THROWN
db.SaveChanges();
return RedirectToAction("Index");
}
return View(aViewModel);
}
Como se muestra en la línea anterior
db.Entry(aViewModel.a).State = EntityState.Modified;
arroja una excepción:
La conexión de una entidad de tipo ''A'' falló porque otra entidad del mismo tipo ya tiene el mismo valor de clave primaria. Esto puede suceder cuando se usa el método ''Adjuntar'' o se establece el estado de una entidad en ''Sin cambios'' o ''Modificado'' si alguna entidad en el gráfico tiene valores clave contradictorios. Esto puede deberse a que algunas entidades son nuevas y aún no han recibido valores clave generados por la base de datos. En este caso, use el método ''Agregar'' o el estado ''Agregado'' de la entidad para rastrear el gráfico y luego establezca el estado de las entidades no nuevas en ''Sin cambios'' o ''Modificado'' según corresponda.
¿Alguien ve algo mal en mi código o entiende en qué circunstancias arrojaría dicho error durante la edición de un modelo?
¡Problema resuelto!
Attach
método Attach
podría ayudar a alguien, pero no ayudaría en esta situación ya que el documento ya estaba siendo rastreado mientras se cargaba en la función Edit GET controller. Adjuntar arrojaría exactamente el mismo error.
El problema que encuentro aquí fue causado por la función canUserAccessA()
que carga la entidad A antes de actualizar el estado del objeto a. Esto estaba arruinando la entidad rastreada y estaba cambiando el estado de un objeto a Detached
.
La solución fue modificar canUserAccessA()
para que no se rastreara el objeto que estaba cargando. Se debe AsNoTracking()
función AsNoTracking()
al consultar el contexto.
// User -> Receipt validation
private bool canUserAccessA(int aID)
{
int userID = WebSecurity.GetUserId(User.Identity.Name);
int aFound = db.Model.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
return (aFound > 0); //if aFound > 0, then return true, else return false.
}
Por algún motivo, no pude usar .Find(aID)
con AsNoTracking()
pero realmente no importa ya que podría lograr lo mismo cambiando la consulta.
Espero que esto ayude a cualquiera con un problema similar.
Al igual que lo que Luke Puplett está diciendo, el problema puede deberse a que no se deshace de manera adecuada ni crea su contexto.
En mi caso, tuve una clase que aceptó un contexto llamado ContextService
:
public class ContextService : IDisposable
{
private Context _context;
public void Dispose()
{
_context.Dispose();
}
public ContextService(Context context)
{
_context = context;
}
//... do stuff with the context
Mi servicio de contexto tenía una función que actualiza una entidad que usa un objeto de entidad instanciada:
public void UpdateEntity(MyEntity myEntity, ICollection<int> ids)
{
var item = _context.Entry(myEntity);
item.State = EntityState.Modified;
item.Collection(x => x.RelatedEntities).Load();
myEntity.RelatedEntities.Clear();
foreach (var id in ids)
{
myEntity.RelatedEntities.Add(_context.RelatedEntities.Find(id));
}
_context.SaveChanges();
}
Todo esto estaba bien, mi controlador donde inicié el servicio fue el problema. Mi controlador originalmente se veía así:
private static NotificationService _service =
new NotificationService(new NotificationContext());
public void Dispose()
{
}
Lo cambié a esto y el error desapareció:
private static NotificationService _service;
public TemplateController()
{
_service = new NotificationService(new NotificationContext());
}
public void Dispose()
{
_service.Dispose();
}
Aquí lo que hice en el caso similar.
Esa situación significa que la misma entidad ya ha existido en el contexto. Así que el seguimiento puede ayudar
Primer control de ChangeTracker si la entidad está en el contexto
var trackedEntries=GetContext().ChangeTracker.Entries<YourEntityType>().ToList();
var isAlreadyTracked =
trackedEntries.Any(trackedItem => trackedItem.Entity.Id ==myEntityToSave.Id);
Si existiera
if (isAlreadyTracked)
{
myEntityToSave= trackedEntries.First(trackedItem => trackedItem.Entity.Id == myEntityToSave.Id).Entity;
}
else
{
//Attach or Modify depending on your needs
}
Este problema también se puede ver durante la ViewModel
de ViewModel
to EntityModel
(usando AutoMapper
, etc.) y tratando de incluir context.Entry().State
y context.SaveChanges()
tal bloque de uso como se muestra abajo resolvería el problema. Tenga en cuenta que el método context.SaveChanges()
se usa dos veces en lugar de usarlo justo después de if-block
ya que también debe estar usando el bloque.
public void Save(YourEntity entity)
{
if (entity.Id == 0)
{
context.YourEntity.Add(entity);
context.SaveChanges();
}
else
{
using (var context = new YourDbContext())
{
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges(); //Must be in using block
}
}
}
Espero que esto ayude...
He agregado esta respuesta solo porque el problema se explica en base a un patrón de datos más complejo y me resulta difícil de entender aquí.
Creé una aplicación bastante simple. Este error ocurrió dentro de la acción Editar POST. La acción aceptó ViewModel como parámetro de entrada. La razón para usar ViewModel fue hacer algunos cálculos antes de guardar el registro.
Una vez que la acción pasó por la validación, como if(ModelState.IsValid)
, mi error fue proyectar los valores de ViewModel en una instancia completamente nueva de Entity. Pensé que tendría que crear una nueva instancia para almacenar datos actualizados y luego guardar dicha instancia.
Lo que más tarde me di cuenta fue que tenía que leer el registro de la base de datos:
Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID);
y actualizado este objeto Todo funciona ahora
Interesantemente:
_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);
O si todavía no es genérico:
_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);
parece resolver mi problema sin problemas.
Logré solucionar el problema actualizando el estado. cuando desencadena el hallazgo o cualquier otra operación de consulta en el mismo registro, el estado se ha actualizado con modificado, por lo que debemos establecer el estado en Independiente, luego puede activar su cambio de actualización
ActivityEntity activity = new ActivityEntity();
activity.name="vv";
activity.ID = 22 ; //sample id
var savedActivity = context.Activities.Find(22);
if (savedActivity!=null)
{
context.Entry(savedActivity).State = EntityState.Detached;
context.SaveChanges();
activity.age= savedActivity.age;
activity.marks= savedActivity.marks;
context.Entry(activity).State = EntityState.Modified;
context.SaveChanges();
return activity.ID;
}
Mi caso fue que no tenía acceso directo al contexto EF desde mi aplicación MVC.
Entonces, si está utilizando algún tipo de repositorio para persistencia de entidad, podría ser apropiado simplemente separar la entidad cargada explícitamente y luego establecer EntityState enlazado a Modificado.
Código de muestra (abstracto):
MVC
public ActionResult(A a)
{
A aa = repo.Find(...);
// some logic
repo.Detach(aa);
repo.Update(a);
}
Repositorio
void Update(A a)
{
context.Entry(a).EntityState = EntityState.Modified;
context.SaveChanges();
}
void Detach(A a)
{
context.Entry(a).EntityState = EntityState.Detached;
}
Parece que la entidad que está tratando de modificar no está siendo rastreada correctamente y, por lo tanto, no se reconoce como editada, sino que se agrega en su lugar.
En lugar de establecer directamente el estado, intente hacer lo siguiente:
//db.Entry(aViewModel.a).State = EntityState.Modified;
db.As.Attach(aViewModel.a);
db.SaveChanges();
Además, me gustaría advertirle que su código contiene una posible vulnerabilidad de seguridad. Si está utilizando la entidad directamente en su modelo de vista, entonces corre el riesgo de que alguien pueda modificar los contenidos de la entidad agregando campos con el nombre correcto en el formulario enviado. Por ejemplo, si el usuario agrega un cuadro de entrada con el nombre "A.Primestre" y la entidad contiene dicho campo, entonces el valor estaría vinculado al modelo de vista y guardado en la base de datos incluso si el usuario no pudiera cambiar eso en el funcionamiento normal de la aplicación .
Actualizar:
Para superar la vulnerabilidad de seguridad mencionada anteriormente, nunca debes exponer tu modelo de dominio como tu modelo de vista sino usar un modelo de vista separado. A continuación, su acción recibiría viewmodel, que podría volver a asignar al modelo de dominio utilizando alguna herramienta de asignación como AutoMapper. Esto lo mantendría a salvo de los usuarios que modifiquen datos confidenciales.
Aquí hay una explicación extendida:
Pensé que compartiría mi experiencia en esta, a pesar de que me siento un poco tonto por no haberme dado cuenta antes.
Estoy usando el patrón de repositorio con las instancias de repo inyectadas en mis controladores. Los repositorios concretos crean una instancia de mi ModelContext (DbContext) que dura la vida útil del repositorio, que es IDisposable
y está dispuesto por el controlador.
El problema para mí fue que tengo un sello y una versión de fila modificados en mis entidades, así que los estaba obteniendo primero para poder compararlos con los encabezados entrantes. Por supuesto, esto cargó y rastreó la entidad que posteriormente se actualizaba.
La solución consistía simplemente en cambiar el repositorio desde la creación de un contexto una vez en el constructor hasta tener los siguientes métodos:
private DbContext GetDbContext()
{
return this.GetDbContext(false);
}
protected virtual DbContext GetDbContext(bool canUseCachedContext)
{
if (_dbContext != null)
{
if (canUseCachedContext)
{
return _dbContext;
}
else
{
_dbContext.Dispose();
}
}
_dbContext = new ModelContext();
return _dbContext;
}
#region IDisposable Members
public void Dispose()
{
this.Dispose(true);
}
protected virtual void Dispose(bool isDisposing)
{
if (!_isDisposed)
{
if (isDisposing)
{
// Clear down managed resources.
if (_dbContext != null)
_dbContext.Dispose();
}
_isDisposed = true;
}
}
#endregion
Esto permite que los métodos del repositorio vuelvan a dar a conocer su instancia de contexto en cada uso al llamar a GetDbContext
, o usar una instancia previa si lo desean especificando true.
Prueba esto:
var local = yourDbContext.Set<YourModel>()
.Local
.FirstOrDefault(f => f.Id == yourModel.Id);
if (local != null)
{
yourDbContext.Entry(local).State = EntityState.Detached;
}
yourDbContext.Entry(applicationModel).State = EntityState.Modified;
Tuve este problema con var local y simplemente lo desprendí así:
if (ModelState.IsValid)
{
var old = db.Channel.Find(channel.Id);
if (Request.Files.Count > 0)
{
HttpPostedFileBase objFiles = Request.Files[0];
using (var binaryReader = new BinaryReader(objFiles.InputStream))
{
channel.GateImage = binaryReader.ReadBytes(objFiles.ContentLength);
}
}
else
channel.GateImage = old.GateImage;
var cat = db.Category.Find(CatID);
if (cat != null)
channel.Category = cat;
db.Entry(old).State = EntityState.Detached; // just added this line
db.Entry(channel).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(channel);
Causas del problema de los objetos cargados con la misma clave, por lo que primero separaremos ese objeto y haremos las actualizaciones para evitar conflictos entre dos objetos con la misma clave.
Tuve un problema similar, después de sondear durante 2-3 días, se descubrió que ".AsNoTracking" debería eliminarse ya que EF no rastrea los cambios y supone que no hay cambios a menos que se adjunte un objeto. Además, si no usamos .AsNoTracking, EF automáticamente sabe qué objeto guardar / actualizar, por lo que no es necesario utilizar Adjuntar / Agregar.
para mí, la copia local fue la fuente del problema. esto lo resolvió
var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
if (local != null)
{
context.Entry(local).State = EntityState.Detached;
}