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 {
1677965234
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) %&g开发者_如何学运维t;
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?
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();
}
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.
Try putting a [ConcurrencyCheck] attribute in your TimeStamp Property
public class PurchaseOrder {
[ConcurrencyCheck]
1677965234
public byte[] Timestamp {get; set;}
}
精彩评论