Saving Changes With Entity Framework 6 in ASP.NET MVC 5

Why isn’t EF saving changes?

If you’re following an older tutorial for ASP.NET MVC and/or Entity Framework, but trying to apply it to the latest versions of the same, you may run into a problem where your controller’s Edit action doesn’t actually save any edits to the database.

You probably have something like the following in your ProductController:

~/Controllers/ProductController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
public ActionResult Edit(Product product)
{
    if (product.ProductID == 0)
    {
        // Since it didn't have a ProductID, we assume this
        // is a new Product
        context.Products.Add(product);
    }

    context.SaveChanges();

    return RedirectToAction("Index");
}

When you test this out, you find that the changes you make aren’t actually committed to the database. What’s going on, EF???

An Excursus on Unit of Work

Entity Framework uses a “unit of work” approach to database operations. Thats basically what all those Context classes are: they define the tables you need for a single operation. When you need to do something, you create the context, do the work, and then end the context after telling EF to SaveChanges.

A typical workflow may be:

  1. Open a context
  2. Query out one or more objects from the database (like Products)
  3. Make changes
  4. Call SaveChanges
  5. Destroy the context

Since EF knows what Products it instantiated, it can examine them, determine what changed, and generate the appropriate query to update the corresponding rows.

But this design pattern breaks down when applied within the stateless architecture of a web session. A single unit of work can’t transcend multiple HTTP requests: the server cleans it up after each response is written out.

In idiomatic MVC, the editing of a row operates like this:

  1. GET Products/Edit/1
    1. Open context
    2. Query out Product 1
    3. Render EditView
    4. Close context
  2. User edits form
  3. POST Products/Edit
    1. Open context
    2. SaveChanges
    3. Close context
    4. Redirect to Index

Recall the example controller above. The product parameter is created externally to Entity Framework. EF did not instantiate the Product based on a query, rather MVC’s ModelBinder instantiated the object based on the POSTed form data. As a result, EF’s SaveChanges method knows absolutely nothing about that object instance.

The Fix

Remember that when the Product is a newly created product (i.e. the ProductID is 0), we had to manually inform EF about its exsistance. This was done by adding the object to the context’s Products collection. This way EF knows about the object and can generate the appropriate INSERT command when SaveChanges is called.

But what about the case when the Edit action is called to save changes to an existing Product? How do we tell EF to track it?

Say hello to the context’s Entry method.

This method allows you to view/change the state of a tracked enitity instance. If the instance is not currently tracked, EF will start tracking it.

All we need to do is add an else clause that informs Entity Framework about the object and sets its state to modified. Thus when SaveChanges is called, EF can generate the needed UPDATE query.

~/Controllers/ProductController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public ActionResult Edit(Product product)
{
    if (product.ProductID == 0)
    {
        // Since it didn't have a ProductID, we assume this
        // is a new Product
        context.Products.Add(product);
    }
    else
    {
        // Since EF doesn't know about this product (it was instantiated by
        // the ModelBinder and not EF itself, we need to tell EF that the
        // object exists and that it is a modified copy of an existing row
        context.Entry(product).State = EnitityState.Modified;
    }

    context.SaveChanges();

    return RedirectToAction("Index");
}

Wrap-Up

ORMs are great, but sometimes the abstraction they provide breaks down in siginifcant ways. When that happens, you often have to dig in and learn how the abstraction actually works so that you can understand and apply the appropriate fixes.

At least this time it wasn’t too difficult.

Further Reading

  1. Entity States and SaveChanges (msdn.microsoft.com)
  2. Using DbContext in EF 4.1 Part 4: Add/Attach and EntityStates (blogs.msdn.com)

Comments