【问题标题】:Optimistic concurrency model in Entity Framework and MVCEntity Framework 和 MVC 中的乐观并发模型
【发布时间】:2010-10-10 01:35:08
【问题描述】:

我在 ASP.NET MVC 控制器中有以下更新代码:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Person(int id, FormCollection form)
{
  var ctx = new DB_Entities(); // ObjectContext
  var person = ctx.Persons.Where(s => s.Id == id).FirstOrDefault(); 
  TryUpdateModel(person, form.ToValueProvider()); 
  ctx.SaveChanges(); 
  return RedirectToAction("Person", id);
}

但是,此更新代码是 Last-Writer-Wins。现在我想添加一些并发控制。 Person 表已经有 SQL 时间戳列。我是否必须将时间戳值作为隐藏值发送给客户端并在回发中手动处理?或者实体框架中是否有标准模式来执行此操作?

谢谢。

【问题讨论】:

    标签: c# asp.net-mvc entity-framework concurrency


    【解决方案1】:

    首先,您需要定义将使用哪些属性或哪些属性来执行并发检查,因为在实体框架中,并发是基于逐个属性定义的。 ConcurrencyMode 用于标记属性以进行并发检查,可以在“实体对象属性”窗口中找到(只需右键单击模型中的 Person 实体)。它的选项是 None(默认)和 Fixed

    在调用 SaveChanges 期间,如果在检索到该行后数据库中的某个字段发生了更改,EF 将取消保存并抛出一个 OptimisticConcurrencyException 如果我们设置了字段的 ConcurrencyModeFixed

    在底层,EF 将该字段的值包含在作为 WHERE 子句提交到数据存储的更新或删除 SQL 语句中。

    如果您想在所有属性上使用 Optimistic Concurrency,只需将 TimeStamp 属性 ConcurrencyMode 设置为 Fixed 如果表中任何字段的值发生更改(而不是在每个属性上将其设置为 Fixed),您将获得 OptimisticConcurrencyException )。

    编辑
    根据下面的 Craig 评论,您需要在视图中保留 TimeStamp 并将其读回 Person 对象,如果您设置ConcurrencyMode 固定在 TimeStamp 属性上。您当然可以尝试处理 EF 可能引发的 OptimisticConcurrencyException,如果您有兴趣,可以通过多种方法从该异常中恢复。

    【讨论】:

    • 他确实需要在视图中包含并发令牌,因为他想知道自 GET 以来是否有人更新了实体。这只有在您知道 GET 期间的值是什么时才有可能。但是,在 POST 期间重新获取实体会使事情变得复杂,因为它会破坏 EF 的内置并发控制。 EF 将使用您在 POST 期间获取的值,而不是来自 GET 的值。
    • 是的,你是对的,他绝对需要从他的视野中读取时间戳。我对他在 POST 中再次阅读 Person 的事实感到困惑。我更新了我的答案。克雷格,非常感谢您对此的意见。
    • 谢谢。我认为 Craig 的解决方法(使用 GetObjectStateEntry.AcceptChanges 假装原始时间戳)效果更好。顺便说一句,我认为根本原因是在 POST 中再次读取了 Person 实体,因此在最初呈现页面时它可能不是同一个实体。这是 EF 中正确的编程模型吗?还有其他方法可以避免这种情况吗?
    【解决方案2】:

    这实际上比它应该做的要难一些。正如 Morteza 所说,除了将并发模式更改为固定之外,您还必须注入您在 GET 期间读取的并发值 before 在 POST 期间更新实体。考虑这一点的方法是,在更新实体之前,您正试图让实体恢复到它在 GET 期间所处的状态。我在这个答案中有一个代码示例:

    ASP.NET MVC Concurrency with RowVersion in Edit Action

    【讨论】:

      【解决方案3】:

      来自 MSDN Saving Changes and Managing Concurrency

      try
      {
          // Try to save changes, which may cause a conflict.
          int num = context.SaveChanges();
          Console.WriteLine("No conflicts. " +
              num.ToString() + " updates saved.");
      }
      catch (OptimisticConcurrencyException)
      {
          // Resolve the concurrency conflict by refreshing the 
          // object context before re-saving changes. 
          context.Refresh(RefreshMode.ClientWins, orders);
      
          // Save changes.
          context.SaveChanges();
          Console.WriteLine("OptimisticConcurrencyException "
          + "handled and changes saved");
      }
      

      【讨论】:

        【解决方案4】:

        我最终在回发功能中这样做了:

        var person = ctx.Persons.Where(s => s.Id == id).FirstOrDefault();
        string ts = form.Get("person_ts"); // get the persisted value from form
        if (person.TimeStamp != ts)
        {
           throw new Exception("Person has been updated by other user");
        }
        TryUpdateModel(person, form.ToValueProvider());     
        // EF will check the timestamp again if the timestamp column's 
        // ConcurrencyMode is set to fixed.
        ctx.SaveChanges();
        

        所以乐观并发检查了两次。只是想知道是否有更好的方法来做到这一点?

        谢谢。

        【讨论】:

        • 我认为您的代码甚至无法编译:运算符 '!=' 不能应用于 'byte[]' 和 'string' 类型的操作数。无论如何,即使你让它被编译,这仍然不是一个强大的编程模型来检查并发性,因为有人可能会在你的 if 语句之后和 SaveChanges() 之前更改行,你会运气不好抓住这个。
        • 对不起,'!=',无论如何你明白了(假设有一些辅助方法来进行比较)。这就是为什么我说在我的方法中进行了两次并发检查:一次在我的“if”语句中,另一次在使用 ConcurrencyMode 的 EF SaveChanges() 中。如果有人在 if 语句之后和 SaveChanges 之前更改了行,EF 将捕获它。
        • Ian,在 POST 期间重新读取实体就好了。这不是必需的,但这样做没有问题。
        猜你喜欢
        • 2011-11-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-02
        • 1970-01-01
        • 1970-01-01
        • 2016-03-02
        相关资源
        最近更新 更多