Handling Optimistic Concurrency Exception with EF and MVC 3

I've written a simple MVC 3 application to demonstrate handing optimistic concurrency exceptions with Entity Framework (EF). Browsers make it easy to test optimistic concurrency; right click on an edit link and select Open in New Tab, then select the same edit link on the page so you have the same record open for edit in two pages. Change a couple values in one window, hit the Save button, then go to the other tab. In the other Edit window, change a field and hit the Save button. The image below shows an example after the second Save.

The model validation errors show the current values (from our previous save). A previously hidden check box Force Save is displayed. You can use the Home link to cancel out of your changes, or you can check the Force Save box and all your changes will be saved.

A best practice is  to use a rowversion  to detect concurrency exceptions. The Product table of the AdventureWorksLT database doesn't include a rowversion field, so I added one with the name seqNum. To enable concurrency checking in the data model, open the EF model file (in my sample ProductsModel.edmx), right click the seqNum property of the Products entity and then click Properties. In the Properties window, change the ConcurrencyMode property to Fixed.

The following code will detect and handle concurrency conflicts:

 [HttpPost]
public ActionResult Edit(Product model) {

    try {

        if (!ModelState.IsValid) { return View(model); }

        db.Products.Attach(model);
        db.ObjectStateManager.ChangeObjectState(model, EntityState.Modified);
        db.SaveChanges();

        return RedirectToAction("Details", new { id = model.ProductID });

    } catch (OptimisticConcurrencyException ocex) {

        // Detach model to get current Product from DB
        db.Products.Detach(model);
        var cp = GetProduct(model.ProductID);
        AddModelErrors(cp, model);
        model.seqNum = cp.seqNum;

        ModelState.AddModelError("", "Optimistic Concurrency Exception occurred. Save Again to override");

    } catch (Exception ex) {
           ModelState.AddModelError("", "See exception and inner exception");  
    }

    return View(model);
}

When an optimistic concurrency exception is caught, the current data base values are fetched and displayed (when they differ with the current form values) as error messages. The current rowversion (sequence number) is also fetched so a subsequent save will persist to the DB in the absence of another change. If another user edits the row and saves it before you submit again, another optimistic concurrency exception will be detected.

 

You can download the sample here.