Updating many-to-many relationship entity framework

asp.net-mvc entity-framework-6

Question

I have problem with updating entites that have many-to many relationship. Below my User and category class:

public class User : IEntity
    {
        [Key]
        public virtual long Id { get; set; }

        private ICollection<Category> _availableCategories;

        public virtual ICollection<Category> AvailableCategories
        {
            get { return _availableCategories ?? (_availableCategories = new List<Category>()); }
            set { _availableCategories = value; }
        }
    }

public class Category : IEntity
    {
        [Key]
        public long Id { get; set; }
        /// <summary>
        /// Full name or description of a category
        /// </summary>
        [StringLength(255)]
        public string FullName { get; set; }
    }

This is code snippet from my repository

public override void Edit(User user)
        {
            var dbUser = _context.Users.Include(x => x.AvailableCategories)
                .Single(x => x.Id == user.Id);
            var categories = _context.Categories;

            dbUser.AvailableCategories.Clear();

            foreach (var cat in user.AvailableCategories)
            {
                dbUser.AvailableCategories.Add(cat);
            }
            _context.Entry(dbUser).State = EntityState.Modified;

        }

However the categories don't get updated. What EF does is insert empty rows into category table and sets relations to this new rows with user.

How can I update User so that I change only categories that already exist in the database?

User that I pass to Edit method has AvailableCategories with only Ids set (rest of properties are empty).

1
0
8/8/2014 12:47:45 PM

Accepted Answer

When you're doing something like posting back M2M relationships, you either must post the full object, as in every single property on those objects, or simply post a list of ids and then use those to query the associated objects back from the database. Otherwise, Entity Framework understands your purpose to be to update the properties on the objects as well, in this case with empty values.

Obviously the first option is quite unwieldy, so the second way is the preferred and standard way. Generally, for this, you'd want to use a view model so you could have a property like the following, that you would post into:

public List<long> SelectedCategories { get; set; }

But, if you insist on using the entity directly, you can get much the same result by simply doing:

var selectedCategories = user.AvailableCategories.Select(m => m.Id)

Once you have the ids:

var newAvailableCategories = _context.Categories.Where(m => selectedCategories.Contains(m.Id));

And then finally set that on your user:

dbUser.AvailableCategories = newAvailableCategories;
0
8/8/2014 3:17:12 PM

Popular Answer

I notice you are also adding the user.AvailableCategories directly into dbUser.AvailableCategories. I've noticed when binding back complex objects from an MVC view that DB Entities are no longer attached to the DbContext. If you look at the entity, you can verify by checking dbContext.Entry(cat).State is "detached" (or something unexpected) I believe.

You must query those entities back out of the dbContext (possibly by using the returned cat.Id's). Or otherwise manually set the entities as "unchanged". And then add those "non-detached" items into dbUser.AvailableCategories. Please see Chris's answer as it shows with specific code how to get this done.

Also, I might use a linking entity. Possibly something like this:

public class UserCategory
{
    public User User {get;set;}
    public Category Category {get;set;}
}

And add it to DB context. Also, drop the linking lists in your current User and Category class. This way you can manipulate the UserCategory class (and DbSet) to manage your many-to-many relationship.



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