Aggiorna una relazione Molti a Molti utilizzando Entity Framework?

c# entity-framework entity-framework-core

Domanda

Ho una tabella di join da molti a molti, con una doppia chiave (PersonId, RoleId). E per semplicità, ho appena ricevuto un PersonId nella mia tabella Person. Inoltre, si usa EF7, che non supporta ancora gran parte della bontà di EF6 (come l'implicità nell'unire le tabelle tramite le proprietà di navigazione).

Considerando che posso eseguire questa query in SQLite e funziona senza problemi: update PersonRole set RoleId = 2 where PersonId = 1 , non posso fare la stessa cosa in EF:

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

L'errore è questo: "Si è verificata un'eccezione non gestita: la proprietà 'RoleId' sul tipo di entità 'PersonRole' fa parte di una chiave e quindi non può essere modificata o contrassegnata come modificata."

(ETA per commento sotto) - il mio modello è:

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

Ho trovato una risposta che includeva un'opzione per eliminare la riga originale (1, 1), quindi reinserirla (1, 2), ma a me sembra inefficiente. È davvero l'unico modo per modificare la relazione?

Risposta accettata

Stai tentando di modificare la chiave di una relazione molti-a-molti da un lato. Le relazioni many-to-many sono rappresentate nel database con una tabella che tiene le chiavi esterne di entrambi i lati nella relazione.

Quello che si tenta di fare, è il tentativo di cambiare la chiave di un oggetto, ma un riferimento è ancora conservato nella tabella delle chiavi esterne, portando a una violazione del vincolo - poiché il valore nella tabella NN non è stato aggiornato.

Questo cambiamento non è consentito in EF7. È necessario utilizzare un comando SQL per farlo invece di prendere in considerazione l'aggiornamento della tabella many-to-many.


Risposta popolare

Puoi usare questa estensione che ho creato per rimuovere il non selezionato e aggiungere il nuovo selezionato alla 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);
    }

usarlo sembra così

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



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché