¿Actualizar una relación muchos a muchos usando Entity Framework?

c# entity-framework entity-framework-core

Pregunta

Tengo una tabla de muchos a muchos, con una clave dual (PersonId, RoleId). Y para simplificar, solo tengo un PersonId en mi tabla de Person. Además, usar EF7, que aún no admite gran parte de la bondad que EF6 (como la implicación de unirse a las tablas a través de las propiedades de navegación).

Mientras que puedo ejecutar esta consulta en SQLite y funciona sin problemas: update PersonRole set RoleId = 2 where PersonId = 1 , no puedo hacer lo mismo en EF:

var du = context.PersonsRoles.Where(p => p.PersonId == 1).First();
du.RoleId = 2;
context.PersonsRoles.Update(du);
context.SaveChanges(); //get an error here

El error es el siguiente: "Se ha producido una excepción no controlada: la propiedad 'RoleId' en el tipo de entidad 'PersonRole' es parte de una clave y, por lo tanto, no se puede modificar ni marcar como modificada".

(ETA por comentario abajo) - mi modelo es:

var du = context.PersonsRoles.Where(p => p.PersonId == 1).First();
du.RoleId = 2;
context.PersonsRoles.Update(du);
context.SaveChanges(); //get an error here

Encontré una respuesta que incluía una opción para eliminar la fila original (1, 1) y luego reinsertarla (1, 2), pero eso me parece ineficiente. ¿Es esa realmente la única manera de modificar la relación?

Respuesta aceptada

Está intentando modificar la clave de una relación de muchos a muchos en un lado. Las relaciones de muchos a muchos se representan en la base de datos con una tabla que contiene las claves externas de ambos lados de la relación.

Lo que intenta hacer es intentar cambiar la clave de un objeto, pero aún se mantiene una referencia en la tabla de clave externa, lo que lleva a una violación de restricción, ya que el valor en la tabla NN no se ha actualizado.

Este cambio no está permitido en EF7. Debe usar un comando SQL para hacerlo en lugar de tener en cuenta la actualización de la tabla muchos a muchos.


Respuesta popular

Puede usar esta extensión que hice para eliminar los no seleccionados y agregar los recién seleccionados a la lista

    public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
    {
        db.Set<T>().RemoveRange(currentItems.Except(newItems, getKey));
        db.Set<T>().AddRange(newItems.Except(currentItems, getKey));
    }

    public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
    {
        return items
            .GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
            .SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
            .Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
            .Select(t => t.t.item);
    }

usándolo se ve así

    public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
    {
        db.Set<T>().RemoveRange(currentItems.Except(newItems, getKey));
        db.Set<T>().AddRange(newItems.Except(currentItems, getKey));
    }

    public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
    {
        return items
            .GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
            .SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
            .Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
            .Select(t => t.t.item);
    }



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué