我们有一个应用程序提供以下错误消息(不时,不经常):
System.Data.Entity.Core.OptimisticConcurrencyException:存储更新, insert或delete语句影响了意外的行数(0)。 自实体加载后,实体可能已被修改或删除。 刷新ObjectStateManager条目
我查看了其他答案并尝试了它们(主键ID为零),但我无法重现。
保存记录的代码实现为命令调用程序。 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();
}
}
有人可以指导我如何解决这个问题吗?我们应该将SaveChanges置于try catch中并捕获OptimisticConcurrencyException吗?我无法重现它,但我们将其投入生产。
答案 0 :(得分:0)
让我们在解决之前了解正在发生的事情。乐观并发意味着你正在抓住记录,假设在你改变之前没有其他人会改变它。悲观并发就是当你锁定记录时,确保没有其他人可以改变任何东西。您很少在断开连接的应用程序中使用悲观,如网站。相反,您捕获错误并确定下一步该做什么。
如果你发现错误,你有很多选择。第一个是提醒用户现在数据库中的内容以及他们要保存的内容并让他们决定。当您再次抓取记录时,可以将其保存回来(如果有人在您保存之前再次更改(非常罕见?),则覆盖)。第二个是简单地覆盖,假设最后胜利。您还可以使用一种方法,根据具体的变化使用不同的方法,例如:
用户A(您的用户)想要更改用户B更改地址的非规范化记录上的电话号码。你可以将两者结合在一起,然后保存。
使用EF,最简单的方法是重新生成记录并覆盖字段然后保存,因为您将获得一个副本,上下文在数据库中识别为未更改。多少&#34;安全&#34;您添加或添加的用户交互是您的选择,并且取决于业务要求。
答案 1 :(得分:0)
这是一篇关于在MVC应用程序中使用EF处理并发性的好文章。
本文是出于所有意图和目的,展示了单层应用程序的示例。但是,它很好地说明了这种方法。它基本上捕获DbUpdateConcurrencyException
并向用户显示原始值,允许他们确定是否继续他们的操作。它还提供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);
}