Updating many-to-many navigation property in Entity Framework 6, changes not being saved

.net c# database entity-framework sql-server

Question

I've been pulling my hair out for about 2 days now, as I simply cannot get EF to save changes whenever I add a many-to-many entity to an existing entity.

My structure is plain simple:

  • I have a table called Person, it has an ID (Primary, identity), and a few other string fields

  • A table called Keyword with an ID (Primary, identity) and a string field called Value

  • and a PersonKeywordRelation, with a PersonId, and a KeywordId field

When I have generated my entities (Database first), I get a class Person, with an ICollection<Keyword> - all good, works as expected.

The problem arises when I try to save an existing Person, with a modified list of keywords. Only the scalar properties (strings) are saved, not my keywords!

  • I've tried disabling Lazy Loading, no effect.
  • I tried loading each individual keyword from the database again, no effect.
  • I tried loading all keywords into the context to see if that would help EF detect changes, it didn't.

I am pretty sure I'm not the only one who have had this problem, (in fact I am entirely sure as I have seen a couple questions on here already, on the same subject, yet I am unable to find a working answer...), mostly for older versions of EF, which is another good reason as to why I started yet another question: Has nothing changed that addresses this issue at all?

Here is my code that does the updating (and creation) of persons. You'll notice my attempt on making EF save changes accordingly.

    public void SavePersons(IList<Person> persons)
    {
        // Create a EF Context
        using (var ctx = new MyDbEntities())
        {
            foreach (var person in persons)
            {
                // Attach
                ctx.Persons.Attach(person);

                // Insert or update?
                ctx.Entry(person).State = person.Id == 0 ? EntityState.Added : EntityState.Modified;

                // Get current keywords before clearing from entity
                var keywords = new List<Keyword>(person.Keywords);

                // Clear keywords from entity, so we can add fresh ones, hopefully
                // EF will have an easier time handling this..
                person.Keywords.Clear();

                // Add keywords
                keywords.ForEach(kw =>
                {
                    ctx.Keywords.Attach(kw);
                    ctx.Entry(kw).State = EntityState.Modified;
                    person.Keywords.Add(kw);
                });            
            }

            // Save
            ctx.SaveChanges();
        }
    }
1
9
1/31/2013 5:09:16 PM

Accepted Answer

Finally.. Finally I can rest! I found the solution! It's not a pretty one, but it works!

Here's the code - sharing is caring.

This is definitely the last time I will work with Entity Framework. Causes more pain & agony than good.

    public void SavePersons(IList<Person> persons)
    {
        // Create a EF Context
        using (var ctx = new MyDbEntities())
        {
            // Iterate
            foreach (var person in persons)
            {
                // Get current keywords
                var keywords = new List<Keyword>(person.Keywords).ToList();

                // Fetch Person from DB (if its not a NEW entry). Must use Include, else it's not working.
                var newPerson = ctx.Persons
                                       .Include("Keywords")
                                       .FirstOrDefault(s => s.Id == person.Id) ?? person;

                // Clear keywords of the object, else EF will INSERT them.. Silly.
                newPerson.Keywords.Clear();

                // Insert or update?
                ctx.Entry(newPerson).State = newPerson.Id == 0 ? EntityState.Added : EntityState.Modified;

                // Apply new scalar values
                if(newPerson.Id != 0)
                {
                    person.Id = newPerson.Id;
                    ctx.Entry(newPerson).CurrentValues.SetValues(person);

                }

                // Iterate through all keywords
                foreach (var kw in ctx.Keywords)
                {
                    // If the current kw exists in OUR list, add it
                    // - if not, remove the relation from the DB.
                    if (keywords.Any(k => k.Id == kw.Id))
                    {
                        //ctx.Entry(kw).State = EntityState.Unchanged;
                        ctx.Keywords.Attach(kw);
                        newPerson.Keywords.Add(kw);
                    }
                    else
                        newPerson.Keywords.Remove(kw);
                }
            }

            // Save
            ctx.SaveChanges();

        }
    }
12
2/1/2013 4:50:34 PM

Popular Answer

Try adding .ToList():

var keywords = new List<Keyword>(person.Keywords).ToList();//generate list sepereate from .Keywords

I'm suspecting that your Keywords list is never populated because you clear it before hydrating it.



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