简体   繁体   中英

ASP.NET MVC Concurrency with RowVersion in Edit Action

I'm wanting to do a simple edit form for our Issue Tracking app. For simplicity, the HttpGet Edit action looks something like this:

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

and then the HttpPost action looks something like this:

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

Which works wonderfully. However, the concurrency check doesn't work because in the Post, I'm re-retreiving the current entity right before I attempt to update it. However, with EF, I don't know how to use the fanciness of SaveChanges() but attach my thisIssue to the context. I tried to call edmx.Issues.Attach(thisIssue) but I get

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.

How do I handle concurrency in MVC with EF and/or how do I properly Attach my edited object to the context?

Thanks in advance

What you are doing is tricky, but can be made to work. Let's presume your timestamp field is called ConcurrencyToken . Obviously, you must include this value in the View and submit it with your form. But you can't simply assign that to the value to thisIssue.ConcurrencyToken in the POST because the EF will remember both the "old" value (the value you fetched from the DB with your call to Single() as well as the "new" value (from your form) and use the "old" value in the WHERE clause. So you need to lie to the EF and assign the correct value. Try this:

    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

You can optimize this by binding only ConcurrencyToken instead of calling TryUpdateModel twice, but this should get you started.

An example of performing optimistic concurrency compatible updates using EF5 follows (the method comes from a repository.) It is assumed that the Entity defines Timestamp with [ConcurrencyCheck] . The concurrency exception occurs upon calling 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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM