Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF5 code first with ASP.NET Web API: Update entity with many-to-many relationship

I'm trying to update a Customer in my database using ASP.NET Web API and Entity Framework 5 code-first, but it's not working. My entities look like this:

public class CustomerModel
{
    public int Id { get; set; }
    public string Name { get; set; }

    // More fields

    public ICollection<CustomerTypeModel> CustomerTypes { get; set; }
}

public class CustomerTypeModel
{
    public int Id { get; set; }
    public string Type { get; set; }

    [JsonIgnore]
    public ICollection<CustomerModel> Customers { get; set; }
}

Nothing all that special. I've built a web interface where users can add a customer by supplying the name and checking one or more customer types. When hitting the submit button, the data is sent to my Web API method:

public void Put([FromBody]CustomerModel customer)
{
    using (var context = new MyContext())
    {
        context.Customers.Attach(customer);
        context.Entry(customer).State = EntityState.Modified;
        context.SaveChanges();
    }
}

This updates the customer fields, but the related customer types are ignored. The incoming customer object does contain a list of CustomerTypes it should be associated with:

[0] => { Id: 1, Type: "Finance", Customers: Null },
[1] => { Id: 2, Type: "Insurance", Customers: Null }
[2] => { Id: 3, Type: "Electronics", Customers: Null }

But instead of looking at this list and adding/removing associated entities, EF just ignores it. New associations are ignored and existing associations remain even if they should be deleted.

I had a similar problem when inserting a customer into the database, this was fixed when I adjusted the state of these entities to EntityState.Unchanged. Naturally, I tried to apply this same magic fix in my update scenario:

public void Put([FromBody]CustomerModel customer)
{
    using (var context = new MyContext())
    {
        foreach (var customertype in customer.CustomerTypes)
        {
            context.Entry(customertype).State = EntityState.Unchanged;
        }

        context.Customers.Attach(customer);
        context.Entry(customer).State = EntityState.Modified;
        context.SaveChanges();
    }
}

But EF keeps displaying the same behavior.

Any ideas on how to fix this? Or should I really just do a manual clear to the list of CustomerTypes and then manually add them?

Thanks in advance.

JP

like image 241
J.P. ten Berge Avatar asked May 16 '13 10:05

J.P. ten Berge


People also ask

What is many-to-many relationships in code first approach?

A many-to-many relationship is defined in code by the inclusion of collection properties in each of the entities - The Categories property in the Book class, and the Books property in the Category class: public class Book. { public int BookId { get; set; }

What is another way to configure a many-to-many relationship in OnModelCreating method?

Step 1 – Add foreign key property to other entities, in the joining entity. Step 2 – Add collection navigation property on the other entities towards the joining entity. Step 3 – Next create DB Context OnModelCreating() method configure both the foreign keys in the joining entity as a composite key using Fluent API.

How do I use code first in Entity Framework?

Step 1 − First, create the console application from File → New → Project… Step 2 − Select Windows from the left pane and Console Application from the template pane. Step 3 − Enter EFCodeFirstDemo as the name and select OK. Step 4 − Right-click on your project in the solution explorer and select Manage NuGet Packages…


1 Answers

This is not really solvable by only setting entity states. You must load the customer from the database first including all its current types and then remove types from or add types to the loaded customer according to the updated types collection of the posted customer. Change tracking will do the rest to delete entries from the join table or insert new entries:

public void Put([FromBody]CustomerModel customer)
{
    using (var context = new MyContext())
    {
        var customerInDb = context.Customers.Include(c => c.CustomerTypes)
            .Single(c => c.Id == customer.Id);

        // Updates the Name property
        context.Entry(customerInDb).CurrentValues.SetValues(customer);

        // Remove types
        foreach (var typeInDb in customerInDb.CustomerTypes.ToList())
            if (!customer.CustomerTypes.Any(t => t.Id == typeInDb.Id))
                customerInDb.CustomerTypes.Remove(typeInDb);

        // Add new types
        foreach (var type in customer.CustomerTypes)
            if (!customerInDb.CustomerTypes.Any(t => t.Id == type.Id))
            {
                context.CustomerTypes.Attach(type);
                customerInDb.CustomerTypes.Add(type);
            }

        context.SaveChanges();
    }
}
like image 163
Slauma Avatar answered Sep 18 '22 23:09

Slauma