Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC + Entity Framework with Concurrency check

I am trying to implement a application following the sample in this page: http://www.asp.net/entity-framework/tutorials/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application

I have a domain class with Timestamp as the concurrency check field:

public class PurchaseOrder {
    [Timestamp]
    public byte[] Timestamp {get; set;}
}

In my Edit.aspx I have the Timestamp as hidden field (I am using a view model):

<%: Html.HiddenFor(model => model.PurchaseOrder.Timestamp) %>

This is my Edit() method:

public ActionResult Edit(int id, FormCollection collection) {
     var purchaseOrder = db.PurchaseOrders.Find(id);
     UpdateModel(purchaseOrder, "PurchaseOrder", collection);

     db.Entry(purchaseOrder).State = EntityState.Modified;
     db.SaveChanges();
}

I opened the same edit page in 2 separate browser at the same time (so that their timestamp is the same), and update them one after the other.

When I update the second page, I expected a DbUpdateConcurrencyException. But I am getting none.

What I think happened is that in the second page, I am getting the purchaseOrder object again from the DB in the Edit action:

var purchaseOrder = db.PurchaseOrders.Find(id);

So the timestamp is the new timestamp because of the previous edit.

But I expected the UpdateModel() to replace the Timestamp value from the formcollection. Obviously, this is not the case.

How can I set the value of the Timestamp of the retrieved purchaseOrder to the in the hidden field, so that the concurrency will be detected?

like image 858
swirlobt Avatar asked May 10 '11 07:05

swirlobt


People also ask

How do you configure entity framework for optimistic concurrency?

To enable optimistic concurrency in Entity Framework Core, you can take advantage of the ConcurrencyCheck attribute. Assume that an update or delete operation is performed on an entity that has the ConcurrencyCheck attribute set on one or more of the properties of the entity.

How do you resolve concurrency issues in C#?

How can we solve concurrency problems? Concurrency problems can be solved by implementing a proper "Locking strategy". Locks prevent action on a resource to be performed when some other resource is already performing some action on it.


2 Answers

It doesn't work this way. Once you load entity by Find you cannot change its timestamp directly. The reason is that timestamp is computed column. EF holds internally original and current values for each loaded entity. If you change the value in the loaded entity, only current value is changed and during update EF compares the original value with the current value to know which columns must be updated. But in case of computed columns EF don't do that and because of that your changed value will never be used.

There are two solutions. The first is not loading the entity from database:

public ActionResult Edit(int id, FormCollection collection) 
{
     // You must create purchase order without loading it, you can use model binder
     var purchaseOrder = CreatePurchaseOrder(id, collection);
     db.Entry(purchaseOrder).State = EntityState.Modified;
     db.SaveChanges();
}

The second solution is small hack described in linked question for ObjectContext API. If you need this for DbContext API you can try something like:

public ActionResult Edit(int id, FormCollection collection) 
{
     var purchaseOrder = db.PurchaseOrders.Find(id);
     purchaseOrder.Timestamp = GetTimestamp(collection);
     // Overwrite original values with new timestamp
     context.Entry(purchaseOrder).OriginalValues.SetValues(purchaseOrder);
     UpdateModel(purchaseOrder, "PurchaseOrder", collection);
     db.SaveChanges();
}
like image 175
Ladislav Mrnka Avatar answered Nov 07 '22 00:11

Ladislav Mrnka


We have overriden the DbContext class, and the SaveChanges method. In it, we look for the TimeStamp values, and if it does not match the value in the OriginalValues collection, we throw an exception.

we have a BaseEntity type for each entity, and it has a SI_TimeStamp column which is of type TimeStamp.

public override int SaveChanges()
{
        foreach (var item in base.ChangeTracker.Entries<BaseEntity>().Where(r => r.State != System.Data.EntityState.Deleted && 
                                                                                 r.State != System.Data.EntityState.Unchanged))
            if (!item.Entity.SI_TimeStamp.ValueEquals(item.OriginalValues.GetValue<byte[]>("SI_TimeStamp")))
                throw new Exception("The entity you are trying to update has ben changed since ....!");
}

you have to place the original value in your forms. Html.HidderFor (r => r.SI_TimeStamp)

I would actually recommend you to check the timestamp against the original value either when loading or after loading the entity. The overriden DbContext class method is a general solution, and it actually makes sense to check against the timestamp value before trying to save changes back to database.

like image 26
hazimdikenli Avatar answered Nov 07 '22 02:11

hazimdikenli