How to update many-to-many relationship in Entity Framework 7?

asp.net entity-framework entity-framework-core

Question

tl;dr: I have some database with a many-to-many relationship. When I edit its data and call SaveChanges(), nothing is being changed in the database.

Full Question:

I encountered a really strange problem. I want to update a many-to-many relationship, but it doesn't want to change.

My Models look like this (I've posted only this part that is important in this question):

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; }
    [...]
}

What I do to update this db relationship:

Get the current state of Note from the database:

Note noteInDb = db.Notes.Include(x => x.NoteTags).ThenInclude(x => x.Tag).AsNoTracking().FirstOrDefault(x => x.NoteId == note.NoteId);

Clear List<NoteTags> and add a new one:

noteInDb.NoteTags = new List<NoteTag>();
noteInDb = TagsToAddToTags(note);

What TagsToAddToTags does: it finds proper Tags in the database and adds them to the List<> like that:

note.NoteTags.Add(new NoteTag { Tag = tagInDb });

Then I tell the database that I've modified some data and save it:

db.Entry(noteInDb).State = EntityState.Modified;
db.SaveChanges();

The noteInDb variable looks exactly how I want it to look when I'm saving it to the database. Unfortunately nothing is being saved, and when I view the database, it still looks the same as before.

The weird fact is also that when I write this line just after SaveChanges():

Note noteInDb2 = db.Notes.Include(x=>x.NoteTags).ThenInclude(x=>x.Tag).FirstOrDefault(x => x.NoteId == note.NoteId);

I can see that noteInDb2 has 9 NoteTags (it has 4 of them before, and I wanted it to have 5, so this is in fact sum of what was before and what I wanted to add). But when I go to LINQPad to see what it really looks like, it shows me 4 old NoteTags (no changes).

What am I doing wrong?

1
3
3/6/2019 11:54:06 PM

Popular Answer

You can use this extension I made to remove the unselected items and add the newly selected items to the list:

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

Using it looks like this:

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);
2
3/6/2019 10:46:38 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow