简体   繁体   English

如何在MVC应用程序中修复OptimisticConcurrencyException

[英]How to fix OptimisticConcurrencyException in an MVC application

We have an application that is giving the following error message (from time to time, not often): 我们有一个应用程序给出以下错误消息(不时,不经常):

System.Data.Entity.Core.OptimisticConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). System.Data.Entity.Core.OptimisticConcurrencyException:存储更新,插入或删除语句影响了意外的行数(0)。 Entities may have been modified or deleted since entities were loaded. 自加载实体以来,实体可能已被修改或删除。 Refresh ObjectStateManager entries 刷新ObjectStateManager条目

I looked at other answers and tried them (primary key id being zero), but I'm not able to reproduce. 我查看了其他答案并进行了尝试(主键ID为零),但我无法重现。

The code that saves records is implemented as a command invoker. 保存记录的代码被实现为命令调用程序。 The Execute method receives the command object to save, and calls the Handle method of the handler: Execute方法接收要保存的命令对象,并调用处理程序的Handle方法:

public class CommandInvoker : ICommandInvoker
{
    private readonly IKernel _kernel;
    private readonly IArhomaContext _context;

    public CommandInvoker(IKernel kernel, IArhomaContext context)
    {
        _kernel = kernel;
        _context = context;
    }

    public void Execute<T>(T command)
    {
        var handler = _kernel.TryGet<ICommandHandler<T>>();
        handler.Handle(command);
        _context.SaveChanges();
    }
}

Can someone guide me as to how we can fix this problem? 有人可以指导我如何解决此问题吗? Should we put the SaveChanges inside a try catch and catch an OptimisticConcurrencyException? 我们是否应该将SaveChanges放入try catch并捕获OptimisticConcurrencyException? I'm unable to reproduce it, but we get this in production. 我无法复制它,但是我们可以在生产中使用它。

Let's get to some understanding of what is happening before solutioning. 在解决之前,让我们先了解一下正在发生的事情。 Optimistic concurrency means you are grabbing the record assuming nobody else will change it before you change back. 乐观并发意味着您在获取记录之前假设没有其他人会更改它。 Pessimistic concurrency is when you lock the record, ensuring nobody else can change anything. 悲观并发是指您锁定记录时,确保没有其他人可以更改任何内容。 You rarely use pessimistic in a disconnected application, like a website. 您很少在断开连接的应用程序(例如网站)中使用悲观情绪。 Instead, you capture the error and determine what to do next. 而是,您捕获错误并确定下一步要做什么。

You have a lot of options in what to do if you catch errors. 如果发现错误,您有很多选择。 The first is to alert the user of what is in the database now and what they were going to save and let them decide. 首先是要警告用户当前数据库中的内容以及他们将要保存的内容并让他们决定。 As you have grabbed the record again, you can save it back (and if someone changes again before you save (very rare?), overwrite). 当您再次获取记录时,可以将其保存回去(如果在保存之前有人再次更改(非常罕见?),则将其覆盖)。 The second is to simply overwrite, assuming last in wins. 第二个是简单地改写,假设最终获胜。 You can also use an approach where you use different methods depending on what change, as an example: 您还可以使用一种方法,其中根据发生的变化使用不同的方法,例如:

User A (your user) wants to change telephone number on a denormalized record that user B changed the address. 用户A(您的用户)希望在非规范化记录上更改用户B更改地址的电话号码。 You can combine the two, behind the scenes, and save. 您可以在后台将两者结合起来并保存。

With EF, the easiest method is to repull the record and overwrite fields and then save, as you will then have a copy the context recognizes as unchanged in the database. 使用EF,最简单的方法是重新刷新记录并覆盖字段,然后保存,因为这样您将获得一个上下文被数据库识别为未更改的副本。 How much "safety" you add, or user interaction you add, is your choice, and depends on business requirements. 您添加多少“安全性”或添加用户交互是您的选择,并且取决于业务需求。

Here is a great article on handling concurrency with EF in a MVC application. 这是一篇有关在MVC应用程序中使用EF处理并发性的出色文章。

http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net- mvc应用

The article is for all intents and purposes showing an example of a single-tier appplication. 本文出于所有目的和目的显示了单层应用程序的示例。 However, it illustrates the approach pretty well. 但是,它很好地说明了该方法。 It basically catches DbUpdateConcurrencyException and displays the original values to the user, allowing them to determine whether or not to continue with their action. 它基本上捕获DbUpdateConcurrencyException并向用户显示原始值,使他们能够确定是否继续其操作。 It also provides for handling of RetryLimitExceededException . 它还提供了RetryLimitExceededException处理。

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(int? id, byte[] rowVersion)
{
    string[] fieldsToBind = new string[] { "Name", "Budget", "StartDate", "InstructorID", "RowVersion" };

    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    var departmentToUpdate = await db.Departments.FindAsync(id);
    if (departmentToUpdate == null)
    {
        Department deletedDepartment = new Department();
        TryUpdateModel(deletedDepartment, fieldsToBind);
        ModelState.AddModelError(string.Empty,
            "Unable to save changes. The department was deleted by another user.");
        ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", deletedDepartment.InstructorID);
        return View(deletedDepartment);
    }

    if (TryUpdateModel(departmentToUpdate, fieldsToBind))
    {
        try
        {
            db.Entry(departmentToUpdate).OriginalValues["RowVersion"] = rowVersion;
            await db.SaveChangesAsync();

            return RedirectToAction("Index");
        }
        catch (DbUpdateConcurrencyException ex)
        {
            var entry = ex.Entries.Single();
            var clientValues = (Department)entry.Entity;
            var databaseEntry = entry.GetDatabaseValues();
            if (databaseEntry == null)
            {
                ModelState.AddModelError(string.Empty,
                    "Unable to save changes. The department was deleted by another user.");
            }
            else
            {
                var databaseValues = (Department)databaseEntry.ToObject();

                if (databaseValues.Name != clientValues.Name)
                    ModelState.AddModelError("Name", "Current value: "
                        + databaseValues.Name);
                if (databaseValues.Budget != clientValues.Budget)
                    ModelState.AddModelError("Budget", "Current value: "
                        + String.Format("{0:c}", databaseValues.Budget));
                if (databaseValues.StartDate != clientValues.StartDate)
                    ModelState.AddModelError("StartDate", "Current value: "
                        + String.Format("{0:d}", databaseValues.StartDate));
                if (databaseValues.InstructorID != clientValues.InstructorID)
                    ModelState.AddModelError("InstructorID", "Current value: "
                        + db.Instructors.Find(databaseValues.InstructorID).FullName);
                ModelState.AddModelError(string.Empty, "The record you attempted to edit "
                    + "was modified by another user after you got the original value. The "
                    + "edit operation was canceled and the current values in the database "
                    + "have been displayed. If you still want to edit this record, click "
                    + "the Save button again. Otherwise click the Back to List hyperlink.");
                departmentToUpdate.RowVersion = databaseValues.RowVersion;
            }
        }
        catch (RetryLimitExceededException /* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
        }
    }
    ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", departmentToUpdate.InstructorID);
    return View(departmentToUpdate);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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