Handling Concurrency Exceptions9:39 with James Churchill
Now that we have our data concurrency control implemented, let's see how we can update our action method to catch and handle the DbUpdateConcurrencyException that EF is throwing in order to display a friendly message to the user.
Now, that we have our data concurrency control implemented, 0:00 we could update our action method to catch and 0:04 handle the DB update concurrency Exception that EF is throwing. 0:06 That will allow us to display a friendly message to the user. 0:11 To do that, we need to wrap these three lines of code in a try catch statement. 0:14 To quickly add a try catch statement, you can type try and 0:20 press Tab twice and cut and paste our code into the try block. 0:23 Then instead of catching any exception, 0:32 let's only catch the DbUpdateCocurrencyException. 0:35 That way any exception other than a DBUpdateConcurrencyException 0:40 won't get caught by our cat statement. 0:45 Instead of our cat statement, we can now add a model error to the model state 0:47 which would display as a validation message to the end user. 0:51 The comic book being updated has 0:55 already been updated by another user. 0:59 Now, let's test our changes. 1:14 Open the Edit Comment Book page into two tabs for Bone number three. 1:18 Save the first Tab, then save the second Tab. 1:26 And we see our error message instead of a server error. 1:34 That's definitely an improvement over the previous behavior. 1:38 Our current solution doesn't give the user any option other than to cancel their edit 1:41 and return to the Comic Book detail page. 1:46 This type of data concurrency conflict resolution is known as 1:48 server wins because the client's changes are discarded. 1:52 We can also give the user the option to resolve the conflict in the client 1:57 wins manner, which will override the server's values with the client's values. 2:01 Giving the use of the option to force their changes, may not always make sense. 2:06 So you'll need to consider your specific situation and 2:10 determine which approach is the most correct. 2:14 Another improvement that we can make is to let the user know when the entity 2:17 they're trying to update has been deleted by another user. 2:21 Let's start with adding the code to detect if the entity was deleted or just updated. 2:25 To do this we need a reference to the DbUpdateConcurrencyException. 2:30 We'll be varying the model error message depending on whether 2:34 the entity was deleted or updated. 2:37 So let's declare a variable for the message. 2:40 We can use the DbUpdateConcurrencyException object to 2:42 check if the entity has been deleted, or 2:46 just update it since it was last retrieved. 2:48 The exception objects entries property is a collection of DB entity 2:51 entry objects representing the entities that couldn't be saved to the database. 2:55 Since we're updating a single entity, we can simply call the single 3:00 method on the entry's property to get our entity's entry. 3:05 Then we can use the entry's get database values method to query from the database 3:09 the entity's current values. 3:14 If the entity isn't found in the database 3:16 then the get database values method will return null. 3:18 So we can use the nullness of the entity property values variable to 3:21 determine what message to display to the user. 3:26 Now we can assign a value to the message variable for each of our use cases. 3:29 First, the deleted use case. 3:34 The comic book being updated has been deleted by 3:37 another user, and then the updated use case, 3:42 The comic book being updated has already been updated by another user. 3:50 And while we're at it, 3:59 let's update the call to the add model air method to use the message variable. 4:01 Okay, so we've updated our solution to let the user know when the entity has been 4:14 deleted or updated. 4:19 But we still don't support allowing the user to choose the option 4:20 to resolve the conflict in a ClientWins manner. 4:24 Implementing that feature is really pretty straight forward. 4:27 We just need to update the comic book's tracking property 4:31 with the current value from the database. 4:34 That way if the user chooses to submit the form again, 4:36 the tracking property value should match the record's current road version 4:40 value in the database, which will allow the update to succeed. 4:44 Remember we already retrieved the entity's current values from the database by 4:49 calling the get database values method on the entity's associated entry object. 4:53 The entity property values variable is a dictionary of property values. 4:59 So we can simply index into the dictionary using the property name we 5:04 wanna value for. 5:08 Indexing into the dictionary returns an object, which can't 5:10 be implicitly converted to the row version properties data type of byte array. 5:13 We can fix this by adding an explicit cast. 5:18 While this approach will work, its reliance on a string for 5:21 the property name is a bit of a code smell. 5:25 Luckily, the entity property values, DBPropertyValues object, 5:27 gives us a work around We can call the ToObject method to get an instance of 5:31 the underlying type, populated with the values from the dictionary. 5:36 The ToObject method returns an object, so 5:41 we need to cast that to the ComicBook type. 5:44 But then we can just access the RowVersion property. 5:46 Now that we've given the user the option to choose the client wins option, 5:50 let's update the error message to let them know that they can do that. 5:53 If you still want to make your changes, 5:59 then click the Save button again. 6:04 Otherwise click the Cancel button 6:11 to discard your changes. 6:17 Let's make one more change before we test our changes. 6:20 Currently when the Cancel button is clicked, 6:23 we send the user to the comic book detail page, but if the entity has been deleted 6:27 redirecting the user to that page will result in a 404 not found error. 6:32 To resolve this issue we can set a flag on the view model to indicate that the entity 6:37 has been deleted. 6:41 We can use that flag in the view to determine the correct URL with the cancel 6:42 button. 6:47 For the flag, 6:48 let's add a boolean property to view model named ComicBookHasBeenDeleted. 6:49 Then we can set the property to true when we detect 6:55 that the entity has been deleted. 6:57 Now we can update the cancel button to use our view model flag to conditionally set 7:01 the anchored elements href attribute. 7:06 If the Comic Book has been deleted, let's set the URL to the list page. 7:08 Otherwise, we'll set it to the detail page, which was the existing behavior. 7:13 And finally, let's update the error message so 7:31 that the user will know what will happen when they click the cancel button. 7:34 Click the Cancel button to return to the list page. 7:38 And now, lets test our changes. 7:46 I'll start by adding a new Comic Book, 7:52 so we can test the deleted concurrency use case. 7:54 I'll select Bone for the series and set the issue number to 4. 7:57 Jeff Smith for the artist, and Script for the role. 8:07 And click the Save button. 8:13 Let's test the updated use case. 8:15 Open the Edit Comic Book page into another tab. 8:17 Save the first tab. 8:22 Then attempt to save the second tab. 8:25 Okay, here's our new error message. 8:32 Hovering over the Cancel button show us that we'd browse back to the comic book 8:35 detail page if we click the button. 8:38 Let's try clicking the Save button. 8:41 Great, we were able to save by choosing the client wins option. 8:44 Now let's tested deleted use case. 8:49 To start let's open a comic book detail page into another tab and 8:51 click the edit button to browse to Edit Comic Book page. 8:55 Then back in the other tab let's delete the Comic Book. 9:01 We're back at the list page and we can see that Bone #4 isn't in the list. 9:11 Let's switch to the other tab and try to Save. 9:17 And we get the error message letting us know that the comic book has been deleted. 9:23 And clicking the Cancel button takes us back to the list page. 9:29 Just like we wanted it to. 9:33 Next step, we'll see how to handle concurrency when deleting records 9:34
You need to sign up for Treehouse in order to download course files.Sign up