I am working on catching concurrency exceptions. I am able to catch the concurrency exceptions when a user edits the data fine. I am having trouble catching the exception when a user deletes data.
On my Index page, I have a button to delete each Vehicle object. Pressing that button does a Post to the Delete action. Here is the Delete action:
[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");
}
}
No matter what, the user should be redirected to the Index page. Here is the Index page’s Get action:
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);
}
The code never catches the concurrency error. I test by opening the index page twice. On one of the pages, I open the edit page of a vehicle and change something. Once that’s saved, I go back to the other page and click “Delete.” The Delete action fires, and the vehicle is deleted, but the concurrency error is not caught. You can see where I commented out vehicle.Timestamp = vehicleIndexViewModel.Timestamp;. I thought putting the value of the viewModel back into the actual would raise the error, but it doesn’t work that way either.
I’m sure there’s just something I don’t understand, but what am I doing wrong?
EDIT
Erik Philips found the logic error that I had, but there was another issue that I ran into right away. My ViewModel was not returning the Timestamp data. In fact, the only data it was returning was the VehicleID.
When I tried to add a hidden field to the form, I would get an error. The code just below this would not work:
<input type="hidden" name="Timestamp" value="@item"/>
The Timestamp field needs to be a valid Base-64 string. The error you will get is:
The input is not a valid Base-64 string as it contains a non-base 64 character, more than >two padding characters, or a non-white space character among the padding characters.
This is how I ended up storing the Timestamp value on the view:
<input type="hidden" name="Timestamp" value="@Convert.ToBase64String(item.Timestamp)"/>
So, my whole Html.BeginForm looks like this:
@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("\"", "")?');"/>
}
Although, I really didn’t need to put VehicleName in a hidden field.
Once that was done, all I needed to do was use AutoMapper to map the values back into a vehicle object, set the EntityState to Deleted, and try to 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 } });
}
That’s it!
Here is what I see would happen if these events happened in this order:
User 1
User 2
User 1
User2
Basically all you’re doing is retrieve the Vehicle in it’s current state (updated from user 1) and deleting it. Unless, by some freak chance, someone did an update between
FindandSaveChangesthere will never be aDbUpdateConcurrencyException.What I believe you can do instead would be to Attach the object to the context, delete it, then call
SaveChanges().