ASP.NET MVC: falló al adjuntar una entidad de tipo 'MODELNAME' porque otra entidad del mismo tipo ya tiene el mismo valor de clave principal

Resuelto Chris Ciszak asked hace 10 años • 21 respuestas

En pocas palabras, la excepción se produce durante la publicación del modelo contenedor y al cambiar el estado de una entrada a "Modificado". Antes de cambiar el estado, el estado se establece en "Separado", pero llamar a Attach() genera el mismo error. Estoy usando EF6.

Encuentre mi código a continuación (los nombres de los modelos se han cambiado 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 arriba de la línea

db.Entry(aViewModel.a).State = EntityState.Modified;

lanza una excepción:

No se pudo adjuntar una entidad de tipo 'A' porque otra entidad del mismo tipo ya tiene el mismo valor de clave principal. Esto puede suceder cuando se utiliza 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 en conflicto. 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, utilice el método 'Agregar' o el estado de entidad 'Agregado' 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 incorrecto en mi código o entiende en qué circunstancias se produciría ese error durante la edición de un modelo?

Chris Ciszak avatar Apr 21 '14 23:04 Chris Ciszak
Aceptado

¡Problema resuelto!

AttachEl método 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 Editar controlador GET. Adjuntar arrojaría exactamente el mismo error.

El problema que encuentro aquí fue causado por una 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. La función AsNoTracking()debe llamarse mientras se consulta 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 alguna razón no pude usarlo .Find(aID), 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!

Chris Ciszak avatar Apr 22 '2014 18:04 Chris Ciszak

Curiosamente:

_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);

O si aún no eres genérico:

_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);

Parece resolver mi problema sin problemas.

guneysus avatar Aug 02 '2015 11:08 guneysus

Parece que la entidad que está intentando modificar no está siendo rastreada correctamente y por lo tanto no se reconoce como editada, sino que se agrega.

En lugar de configurar el estado directamente, 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, corre el riesgo de que alguien pueda modificar el contenido de la entidad agregando campos con el nombre correcto en el formulario enviado. Por ejemplo, si el usuario agregó un cuadro de entrada con el nombre "A.FirstName" y la entidad contenía dicho campo, entonces el valor se vincularía al modelo de vista y se guardaría en la base de datos incluso si al usuario no se le permitiría cambiarlo en el funcionamiento normal de la aplicación. .

Actualizar:

Para superar la vulnerabilidad de seguridad mencionada anteriormente, nunca debe exponer su modelo de dominio como su modelo de vista, sino utilizar un modelo de vista separado. Luego, su acción recibiría un modelo de vista que podría asignar al modelo de dominio utilizando alguna herramienta de mapeo como AutoMapper. Esto lo mantendrá a salvo de que el usuario modifique datos confidenciales.

Aquí hay una explicación ampliada:

http://www.stevefenton.co.uk/Content/Blog/Date/201303/Blog/Why-You-Never-Expose-Your-Domain-Model-As-Your-MVC-Model/

Kaspars Ozols avatar Apr 21 '2014 19:04 Kaspars Ozols

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;
                }
add-Naan avatar Sep 14 '2015 16:09 add-Naan