【问题标题】:ASP.NET MVC + Entity Framework with Concurrency check带有并发检查的 ASP.NET MVC + 实体框架
【发布时间】:2011-05-10 07:52:24
【问题描述】:

我正在尝试按照此页面中的示例实现一个应用程序: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。但我没有得到。

我认为发生的事情是,在第二页中,我在 Edit 操作中再次从数据库中获取 purchaseOrder 对象:

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

所以时间戳是新的时间戳,因为之前的编辑。

但我希望 UpdateModel() 替换 formcollection 中的 Timestamp 值。 显然,情况并非如此。

如何将检索到的purchaseOrder的Timestamp的值设置为隐藏字段中的,以便检测到并发?

【问题讨论】:

  • 我今天遇到了同样的问题。拉迪斯拉夫的想法是正确的。您需要将 EF 保留的原始值更改为来自您的表单的值。有几种方法可以做到这一点。

标签: asp.net-mvc entity-framework concurrency entity-framework-4.1


【解决方案1】:

It doesn't work this way。通过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();
}

第二种解决方案是 ObjectContext API 的链接问题中描述的小技巧。如果您需要 DbContext API,您可以尝试以下方法:

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】:

    我们已经覆盖了 DbContext 类和 SaveChanges 方法。在其中,我们查找 TimeStamp 值,如果它与 OriginalValues 集合中的值不匹配,则抛出异常。

    每个实体都有一个 BaseEntity 类型,它有一个 TimeStamp 类型的 SI_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 类方法是一种通用解决方案,实际上在尝试将更改保存回数据库之前检查时间戳值是有意义的。

    【讨论】:

    • 正如 Ladislav 所说,EF 将检查 EF 加载实体后所做的更改。
    • +1 我太专注于 EF,以至于我忘记提及这一点,即使我自己使用了类似的解决方案。如果您可以直接在 .net 代码中比较时间戳,则没有理由往返数据库。以下是关于时间戳比较的一些想法:stackoverflow.com/questions/4672193/…
    【解决方案3】:

    尝试在 TimeStamp 属性中添加 [ConcurrencyCheck] 属性

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-17
      • 2011-12-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多