ASP.NET MVC + Entity Framework with Concurrency check

ぃ、小莉子 提交于 2019-12-03 13:02:26

问题


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?


回答1:


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();
}



回答2:


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.




回答3:


Try putting a [ConcurrencyCheck] attribute in your TimeStamp Property

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


来源:https://stackoverflow.com/questions/5946883/asp-net-mvc-entity-framework-with-concurrency-check

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!