tl; dr: ho un database con una relazione molti-a-molti. Quando modifico i suoi dati e chiamo SaveChanges()
, nulla viene modificato nel database.
Domanda completa:
Ho riscontrato un problema davvero strano. Voglio aggiornare una relazione molti-a-molti, ma non vuole cambiare.
Le mie modelle si presentano così (ho pubblicato solo questa parte che è importante in questa domanda):
public class Note
{
public Note()
{
NoteTags = new HashSet<NoteTag>()
}
public int NoteId { get; set; }
public virtual ICollection<NoteTag> NoteTags { get; set; }
[...]
}
public class NoteTag
{
public int NoteId { get; set; }
public Note Note { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
public class Tag
{
public Tag()
{
NoteTags = new HashSet<NoteTag>();
}
public int TagId { get; set; }
public string Name { get; set; }
public virtual ICollection<NoteTag> NoteTags { get; set; }
[...]
}
Cosa devo fare per aggiornare questa relazione db:
Ottieni lo stato attuale della Note
dal database:
Note noteInDb = db.Notes.Include(x => x.NoteTags).ThenInclude(x => x.Tag).AsNoTracking().FirstOrDefault(x => x.NoteId == note.NoteId);
Cancella List<NoteTags>
e aggiungine uno nuovo:
noteInDb.NoteTags = new List<NoteTag>();
noteInDb = TagsToAddToTags(note);
Cosa fa TagsToAddToTags
: trova i Tags
corretti nel database e li aggiunge List<>
questo modo:
note.NoteTags.Add(new NoteTag { Tag = tagInDb });
Quindi dico al database che ho modificato alcuni dati e li salvo:
db.Entry(noteInDb).State = EntityState.Modified;
db.SaveChanges();
La variabile noteInDb
appare esattamente come voglio che appaia quando la sto salvando nel database. Sfortunatamente non viene salvato nulla e, quando visualizzo il database, sembra ancora lo stesso di prima.
Il fatto strano è anche che quando scrivo questa riga subito dopo SaveChanges ():
Note noteInDb2 = db.Notes.Include(x=>x.NoteTags).ThenInclude(x=>x.Tag).FirstOrDefault(x => x.NoteId == note.NoteId);
Vedo che noteInDb2
ha 9 NoteTag
(ne ha già 4 e volevo che ne avesse 5, quindi in realtà è la somma di ciò che era prima e di ciò che volevo aggiungere). Ma quando vado su LINQPad per vedere come appare realmente, mi mostra 4 vecchi NoteTag
(nessuna modifica).
Che cosa sto facendo di sbagliato?
È possibile utilizzare questa estensione che ho creato per rimuovere gli elementi non selezionati e aggiungere gli elementi appena selezionati all'elenco:
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);
}
Usandolo si presenta così:
var model = db.Notes
.Include(x => x.NoteTags)
.FirstOrDefault(x => x.NoteId == note.NoteId);
db.TryUpdateManyToMany(model.NoteTags, listOfNewTagIds
.Select(x => new NoteTag
{
TagId = x,
NoteId = note.NoteId
}), x => x.TagId);