我正在捕捉并发异常。当用户对数据进行精细编辑时,我能够捕捉到并发异常。当用户删除数据时,我很难捕捉到异常。
在我的索引页面上,我有一个删除每个车辆对象的按钮。按下该按钮将发布到删除操作。以下是删除操作:
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(VehicleIndexViewModel vehicleIndexViewModel)
{
try
{
Vehicle vehicle = db.Vehicles.Find(vehicleIndexViewModel.VehicleID);
//To test for concurrency errors
//vehicle.Timestamp = vehicleIndexViewModel.Timestamp;
db.Entry(vehicle).State = EntityState.Deleted;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToAction("Index",
new System.Web.Routing.RouteValueDictionary{{"concurrencyError", true }});
}
catch (DataException)
{
//Log the error (add a variable name after Exception)
ModelState.AddModelError(string.Empty, "The system was unable to delete that"
+ " vehicle. Try again, and if the problem persists"
+ " contact your system administrator.");
return RedirectToAction("Index");
}
}
不管怎样,用户都应该重定向到索引页面。以下是索引页面的获取操作:
public ViewResult Index(bool? concurrencyError)
{
if (concurrencyError.GetValueOrDefault())
{
ViewBag.ConcurrencyErrorMessage = "The record you attempted to delete "
+ "was modified by another user after you got the original values. "
+ "The delete operation was canceled and the current values in the "
+ "database have been displayed. If you still want to delete this "
+ "record, click the Delete button again. Otherwise "
+ "click the Back to List hyperlink.";
}
IEnumerable<Vehicle> vehicles = db.Vehicles.Include(v => v.VehicleType);
IEnumerable<VehicleIndexViewModel> viewModel
= Mapper.Map<IEnumerable<Vehicle>,
IEnumerable<VehicleIndexViewModel>>(vehicles);
return View(viewModel);
}
代码永远不会捕捉到并发错误。我通过打开索引页两次进行测试。在其中一个页面上,我打开一辆车的编辑页面,更改了一些内容。保存后,我返回另一个页面并单击"删除"。删除操作启动,车辆被删除,但并发错误没有被捕获 你可以看到我在哪里评论车辆。时间戳=vehicleIndexViewModel.Timestamp;。我原以为把viewModel的值放回实际值会引起错误,但它也不起作用。
我肯定有一些事情我不明白,但我做错了什么?
编辑
Erik Philips发现了我的逻辑错误,但我马上遇到了另一个问题。我的ViewModel没有返回时间戳数据。事实上,它返回的唯一数据是VehicleID。
当我试图在表单中添加隐藏字段时,会出现错误。下面的代码不起作用:
<input type="hidden" name="Timestamp" value="@item"/>
时间戳字段必须是有效的Base-64字符串。您将得到的错误是:
输入不是有效的Base-64字符串,因为它包含非Base-64字符、多于两个的填充字符或填充字符中的非空白字符。
这就是我最终在视图上存储时间戳值的方式:
<input type="hidden" name="Timestamp" value="@Convert.ToBase64String(item.Timestamp)"/>
所以,我的整个Html.BeginForm看起来是这样的:
@using (Html.BeginForm("Delete", "Vehicle", FormMethod.Post, null))
{
<input type="hidden" name="VehicleID" value="@item.VehicleID"/>
<input type="hidden" name="VehicleName" value="@item.VehicleName"/>
<input type="hidden" name="Timestamp" value="@Convert.ToBase64String(item.Timestamp)"/>
<input type="image" src="../../Content/Images/Delete.gif" value="Delete" name="deletevehicle @item.VehicleID" onclick="return confirm('Are you sure you want to delete @item.VehicleName.Replace("'", "").Replace(""", "")?');"/>
}
尽管如此,我真的不需要把VehicleName放在一个隐藏的字段中。
完成后,我所需要做的就是使用AutoMapper将值映射回车辆对象,将EntityState设置为Deleted,并尝试SaveChanges;
try
{
Vehicle vehicle = Mapper.Map<VehicleIndexViewModel, Vehicle>(vehicleIndexViewModel);
db.Entry(vehicle).State = EntityState.Deleted;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToAction("Index", new System.Web.Routing.RouteValueDictionary { { "concurrencyError", true } });
}
就是这样!
如果这些事件按以下顺序发生,我会看到什么:
用户1
车辆/编辑车辆ID=1(时间戳=1)
用户2
车辆/编辑车辆ID=1(时间戳=1)
用户1
车辆/更新车辆ID=1,标题="某些文本",(时间戳=2)
用户2
车辆/删除确认的车辆ID=1,时间戳=1
/* DeleteConfirmed */
// Default Model Binder
VehicleIndexViewModel.VehicleID = 1
VehicleIndexViewModel.Timestamp = 1
Vehicle vehicle = db.Vehicles.Find(vehicleIndexViewModel.VehicleID);
//vehicle.VehicleID = 1
//vehicle.Timestamp = 2
// Set to Delete
db.Entry(vehicle).State = EntityState.Deleted;
// Delete in database
db.SaveChanges();
基本上,你所做的就是检索处于当前状态的车辆(从用户1更新)并将其删除。除非有人在Find
和SaveChanges
之间进行了更新,否则永远不会有DbUpdateConcurrencyException
。
我相信您可以做的是将对象附加到上下文,删除它,然后调用SaveChanges()
。