编辑操作中使用RowVersion的ASP.NET MVC并发

时间:2010-05-05 17:35:03

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

我想为问题跟踪应用做一个简单的编辑表单。为简单起见,HttpGet Edit操作看起来像这样:

    // Issues/Edit/12
    public ActionResult Edit(int id)
    {
        var thisIssue = edmx.Issues.First(i => i.IssueID == id);
        return View(thisIssue);
    }

然后HttpPost动作看起来像这样:

    [HttpPost]
    public ActionResult Edit(int id, FormCollection form)
    {
        // this is the dumb part where I grab the object before I update it.
        // concurrency is sidestepped here.
        var thisIssue = edmx.Issues.Single(c => c.IssueID == id);

        TryUpdateModel(thisIssue);
        if (ModelState.IsValid)
        {
            edmx.SaveChanges();

            TempData["message"] = string.Format("Issue #{0} successfully modified.", id);
            return RedirectToAction("Index");
        }

        return View(thisIssue);
    }

这非常有用。但是,并发检查不起作用,因为在Post中,我正在尝试更新它之前重新检索当前实体。但是,对于EF,我不知道如何使用SaveChanges()的煽动性,而是将thisIssue附加到上下文中。我试着打电话给edmx.Issues.Attach(thisIssue),但我得到了

The object cannot be attached because it is already in the object context. An object can only be reattached when it is in an unchanged state.

如何使用EF处理MVC中的并发和/或如何正确Attach我的编辑对象到上下文?

提前致谢

2 个答案:

答案 0 :(得分:6)

你在做什么是棘手的,但可以使其发挥作用。我们假设您的时间戳字段被称为ConcurrencyToken。显然,您必须在视图中包含此值并将其与表单一起提交。但是你不能简单地将它分配给POST中thisIssue.ConcurrencyToken的值,因为EF会记住“旧”值(你通过调用Single()从数据库中获取的值)作为“新”值(来自您的表单)并使用WHERE子句中的“旧”值。因此您需要骗取EF并指定正确的值。试试这个:

    var thisIssue = edmx.Issues.Single(c => c.IssueID == id);
    TryUpdateModel(thisIssue); // assign ConcurrencyToken
    var ose = Context.ObjectStateManager.GetObjectStateEntry(entityToUpdate);
    ose.AcceptChanges();       // pretend object is unchanged
    TryUpdateModel(thisIssue); // assign rest of properties

您可以仅绑定ConcurrencyToken而不是两次调用TryUpdateModel来优化此功能,但这应该可以让您开始。

答案 1 :(得分:5)

使用EF5执行乐观并发兼容更新的示例如下(该方法来自存储库。)假设实体使用[ConcurrencyCheck]定义时间戳。调用DbContext.SaveChanges()时会发生并发异常。

public TEntity Update(TEntity entity)
{
    var attached = this.GetById(entity.Id);
    if (attached == null)
    {
        throw new MvcBootstrapDataException("{0} with Id = {1} does not exist.".F(typeof(TEntity).Description(), entity.Id));
    }

    var entry = this.Context.Entry(attached);

    // The Timestamp must be in the original values for optimistic concurrency checking to occur.
    // Otherwise the context knows that the Timestamp has been modified in the context
    entry.OriginalValues["Timestamp"] = entity.Timestamp;

    entry.CurrentValues.SetValues(entity);

    attached.Modified = DateTime.Now;

    return attached;
}