ASP.NET MVC + Entity Framework с проверкой параллелизма - PullRequest
8 голосов
/ 10 мая 2011

Я пытаюсь реализовать приложение, следуя примеру на этой странице: http://www.asp.net/entity-framework/tutorials/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application

У меня есть класс домена с отметкой времени в качестве поля проверки параллелизма:

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

В моемEdit.aspx У меня есть скрытая метка времени (я использую модель вида):

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

Это мой метод Edit ():

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

Я открыл то же редактированиестраницы в 2 отдельных браузерах одновременно (чтобы их временная метка была одинаковой) и обновлять их одну за другой.

Когда я обновляю вторую страницу, я ожидал исключение DbUpdateConcurrencyException.Но я ничего не получаю.

Я думаю, что на второй странице я снова получаю объект purchaseOrder из БД в действии Edit:

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

Итак, отметка времениэто новая временная метка из-за предыдущего редактирования.

Но я ожидал, что UpdateModel () заменит значение Timestamp из формы formcollection.Очевидно, что это не так.

Как я могу установить значение метки времени извлеченного приобретенного заказа в значение в скрытом поле, чтобы обнаруживать параллелизм?

Ответы [ 3 ]

6 голосов
/ 10 мая 2011

Так не работает .Как только вы загрузите сущность с помощью Find, вы не сможете напрямую изменить ее метку времени.Причина в том, что отметка времени является вычисляемым столбцом.EF содержит внутренне исходные и текущие значения для каждого загруженного объекта.Если вы изменяете значение в загруженном объекте, изменяется только текущее значение, и во время обновления EF сравнивает исходное значение с текущим значением, чтобы узнать, какие столбцы необходимо обновить.Но в случае вычисляемых столбцов EF этого не делает, и поэтому измененное вами значение никогда не будет использовано.

Существует два решения.Первый не загружает объект из базы данных:

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

Вторым решением является небольшой взлом, описанный в связанном вопросе для API ObjectContext.Если вам это нужно для API DbContext, вы можете попробовать что-то вроде:

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();
}
3 голосов
/ 25 июля 2011

Попробуйте добавить атрибут [ConcurrencyCheck] в свойстве TimeStamp

public class PurchaseOrder {
    [ConcurrencyCheck]
    [Timestamp]
    public byte[] Timestamp {get; set;}
}
3 голосов
/ 10 мая 2011

Мы переопределили класс DbContext и метод SaveChanges.В нем мы ищем значения TimeStamp, и если оно не совпадает со значением в коллекции OriginalValues, мы генерируем исключение.

у нас есть тип BaseEntity для каждой сущности, и он имеет столбец SI_TimeStamp,имеет тип 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 ....!");
}

Вы должны поместить исходное значение в ваши формы.Html.HidderFor (r => r.SI_TimeStamp)

На самом деле я бы порекомендовал вам проверять отметку времени по сравнению с исходным значением либо при загрузке, либо после загрузки объекта.Переопределенный метод класса DbContext является общим решением, и на самом деле имеет смысл сверяться со значением метки времени, прежде чем пытаться сохранить изменения обратно в базу данных.

...