开发者

Optimistic concurrency model in Entity Framework and MVC

I have the following update code in the ASP.NET MVC controller:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Person(int id, FormCollection form)
{
  var ctx = new DB_Entities(); // ObjectContext
  var person = ctx.Persons.Where(s => s.Id == id).FirstOrDefault(); 
  TryUpdateModel(person, form.ToValueProvider()); 
  ctx.SaveChanges(); 
  return RedirectToAction("Person", id);
}

However, this update code is Last-Writer-Wins. Now I want to add some concurrency control. The Person table already has the SQL timestamp column. Do I have to send the timestamp value to the client as the hidden开发者_如何学Python value and process it manually in the post back? Or is there a a standard pattern in Entity Framework to do this?

Thanks.


First you need to define which property or properties will be used to perform the concurrency check, because concurrency is defined on a property-by-property basis in the Entity Framework. ConcurrencyMode is used to flag a property for concurrency checking and can be found in the Entity Object Properties window (just right click on Person entity in your model). Its options are None, which is the default, and Fixed.

During a call to SaveChanges, if a field has been changed in the DB since the row was retrieved, EF will cancel the Save and throw an OptimisticConcurrencyException if we set that field's ConcurrencyMode to Fixed.

Under the hood, EF includes that field's value in the Update or Delete SQL statement that is being Submitted to the data store as a WHERE clause.

If you want to have Optimistic Concurrency on all properties, just set TimeStamp property ConcurrencyMode to Fixed you will get an OptimisticConcurrencyException if any field's value within the table get changed (instead of setting it to Fixed on every single property).

EDIT
As per Craig comment below, you need to persist the TimeStamp in the view and read it back into Person object and the rest will be taken care of by EF if you set the ConcurrencyMode to fixed on the TimeStamp property. You can of course try to handle OptimisticConcurrencyException that could be thrown by EF and there are ways to recover from this exception, if you are interested.


This is actually a little harder than it perhaps should be. In addition to changing the concurrency mode to fixed, as Morteza says, you have to inject the concurrency value which you read during the GET before updating the entity during the POST. The way to think about this is that you're trying to get the entity back into the state it was in during the GET, before updating it. I have a code example in this answer:

ASP.NET MVC Concurrency with RowVersion in Edit Action


From MSDN Saving Changes and Managing Concurrency

try
{
    // Try to save changes, which may cause a conflict.
    int num = context.SaveChanges();
    Console.WriteLine("No conflicts. " +
        num.ToString() + " updates saved.");
}
catch (OptimisticConcurrencyException)
{
    // Resolve the concurrency conflict by refreshing the 
    // object context before re-saving changes. 
    context.Refresh(RefreshMode.ClientWins, orders);

    // Save changes.
    context.SaveChanges();
    Console.WriteLine("OptimisticConcurrencyException "
    + "handled and changes saved");
}


I ended up doing this in the postback function:

var person = ctx.Persons.Where(s => s.Id == id).FirstOrDefault();
string ts = form.Get("person_ts"); // get the persisted value from form
if (person.TimeStamp != ts)
{
   throw new Exception("Person has been updated by other user");
}
TryUpdateModel(person, form.ToValueProvider());     
// EF will check the timestamp again if the timestamp column's 
// ConcurrencyMode is set to fixed.
ctx.SaveChanges();

So the optimistic concurrency is checked twice. Just wondering if there is a better way to do this?

Thanks.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜