1 00:00:00,700 --> 00:00:04,100 Now, that we have our data concurrency control implemented, 2 00:00:04,100 --> 00:00:06,680 we could update our action method to catch and 3 00:00:06,680 --> 00:00:11,100 handle the DB update concurrency Exception that EF is throwing. 4 00:00:11,100 --> 00:00:14,800 That will allow us to display a friendly message to the user. 5 00:00:14,800 --> 00:00:20,050 To do that, we need to wrap these three lines of code in a try catch statement. 6 00:00:20,050 --> 00:00:23,951 To quickly add a try catch statement, you can type try and 7 00:00:23,951 --> 00:00:28,107 press Tab twice and cut and paste our code into the try block. 8 00:00:32,413 --> 00:00:35,041 Then instead of catching any exception, 9 00:00:35,041 --> 00:00:38,580 let's only catch the DbUpdateCocurrencyException. 10 00:00:40,650 --> 00:00:45,340 That way any exception other than a DBUpdateConcurrencyException 11 00:00:45,340 --> 00:00:47,190 won't get caught by our cat statement. 12 00:00:47,190 --> 00:00:51,646 Instead of our cat statement, we can now add a model error to the model state 13 00:00:51,646 --> 00:00:55,282 which would display as a validation message to the end user. 14 00:00:55,282 --> 00:00:59,538 The comic book being updated has 15 00:00:59,538 --> 00:01:04,745 already been updated by another user. 16 00:01:14,166 --> 00:01:16,334 Now, let's test our changes. 17 00:01:18,866 --> 00:01:23,255 Open the Edit Comment Book page into two tabs for Bone number three. 18 00:01:26,681 --> 00:01:31,519 Save the first Tab, then save the second Tab. 19 00:01:34,380 --> 00:01:38,080 And we see our error message instead of a server error. 20 00:01:38,080 --> 00:01:41,500 That's definitely an improvement over the previous behavior. 21 00:01:41,500 --> 00:01:46,190 Our current solution doesn't give the user any option other than to cancel their edit 22 00:01:46,190 --> 00:01:48,840 and return to the Comic Book detail page. 23 00:01:48,840 --> 00:01:52,930 This type of data concurrency conflict resolution is known as 24 00:01:52,930 --> 00:01:57,420 server wins because the client's changes are discarded. 25 00:01:57,420 --> 00:02:01,450 We can also give the user the option to resolve the conflict in the client 26 00:02:01,450 --> 00:02:06,785 wins manner, which will override the server's values with the client's values. 27 00:02:06,785 --> 00:02:10,906 Giving the use of the option to force their changes, may not always make sense. 28 00:02:10,906 --> 00:02:14,042 So you'll need to consider your specific situation and 29 00:02:14,042 --> 00:02:17,440 determine which approach is the most correct. 30 00:02:17,440 --> 00:02:21,420 Another improvement that we can make is to let the user know when the entity 31 00:02:21,420 --> 00:02:25,040 they're trying to update has been deleted by another user. 32 00:02:25,040 --> 00:02:30,540 Let's start with adding the code to detect if the entity was deleted or just updated. 33 00:02:30,540 --> 00:02:34,960 To do this we need a reference to the DbUpdateConcurrencyException. 34 00:02:34,960 --> 00:02:37,740 We'll be varying the model error message depending on whether 35 00:02:37,740 --> 00:02:40,060 the entity was deleted or updated. 36 00:02:40,060 --> 00:02:42,950 So let's declare a variable for the message. 37 00:02:42,950 --> 00:02:46,305 We can use the DbUpdateConcurrencyException object to 38 00:02:46,305 --> 00:02:48,729 check if the entity has been deleted, or 39 00:02:48,729 --> 00:02:51,385 just update it since it was last retrieved. 40 00:02:51,385 --> 00:02:55,273 The exception objects entries property is a collection of DB entity 41 00:02:55,273 --> 00:03:00,770 entry objects representing the entities that couldn't be saved to the database. 42 00:03:00,770 --> 00:03:05,322 Since we're updating a single entity, we can simply call the single 43 00:03:05,322 --> 00:03:09,338 method on the entry's property to get our entity's entry. 44 00:03:09,338 --> 00:03:14,041 Then we can use the entry's get database values method to query from the database 45 00:03:14,041 --> 00:03:16,320 the entity's current values. 46 00:03:16,320 --> 00:03:18,750 If the entity isn't found in the database 47 00:03:18,750 --> 00:03:21,960 then the get database values method will return null. 48 00:03:21,960 --> 00:03:26,445 So we can use the nullness of the entity property values variable to 49 00:03:26,445 --> 00:03:29,656 determine what message to display to the user. 50 00:03:29,656 --> 00:03:34,400 Now we can assign a value to the message variable for each of our use cases. 51 00:03:34,400 --> 00:03:37,020 First, the deleted use case. 52 00:03:37,020 --> 00:03:42,100 The comic book being updated has been deleted by 53 00:03:42,100 --> 00:03:47,053 another user, and then the updated use case, 54 00:03:50,316 --> 00:03:57,940 The comic book being updated has already been updated by another user. 55 00:03:59,962 --> 00:04:01,242 And while we're at it, 56 00:04:01,242 --> 00:04:05,480 let's update the call to the add model air method to use the message variable. 57 00:04:14,830 --> 00:04:19,417 Okay, so we've updated our solution to let the user know when the entity has been 58 00:04:19,417 --> 00:04:20,980 deleted or updated. 59 00:04:20,980 --> 00:04:24,530 But we still don't support allowing the user to choose the option 60 00:04:24,530 --> 00:04:27,920 to resolve the conflict in a ClientWins manner. 61 00:04:27,920 --> 00:04:31,360 Implementing that feature is really pretty straight forward. 62 00:04:31,360 --> 00:04:34,410 We just need to update the comic book's tracking property 63 00:04:34,410 --> 00:04:36,970 with the current value from the database. 64 00:04:36,970 --> 00:04:40,570 That way if the user chooses to submit the form again, 65 00:04:40,570 --> 00:04:44,380 the tracking property value should match the record's current road version 66 00:04:44,380 --> 00:04:49,050 value in the database, which will allow the update to succeed. 67 00:04:49,050 --> 00:04:53,340 Remember we already retrieved the entity's current values from the database by 68 00:04:53,340 --> 00:04:59,260 calling the get database values method on the entity's associated entry object. 69 00:04:59,260 --> 00:05:04,050 The entity property values variable is a dictionary of property values. 70 00:05:04,050 --> 00:05:08,762 So we can simply index into the dictionary using the property name we 71 00:05:08,762 --> 00:05:10,045 wanna value for. 72 00:05:10,045 --> 00:05:13,540 Indexing into the dictionary returns an object, which can't 73 00:05:13,540 --> 00:05:18,470 be implicitly converted to the row version properties data type of byte array. 74 00:05:18,470 --> 00:05:21,940 We can fix this by adding an explicit cast. 75 00:05:21,940 --> 00:05:25,174 While this approach will work, its reliance on a string for 76 00:05:25,174 --> 00:05:27,447 the property name is a bit of a code smell. 77 00:05:27,447 --> 00:05:31,651 Luckily, the entity property values, DBPropertyValues object, 78 00:05:31,651 --> 00:05:36,071 gives us a work around We can call the ToObject method to get an instance of 79 00:05:36,071 --> 00:05:40,430 the underlying type, populated with the values from the dictionary. 80 00:05:41,720 --> 00:05:44,200 The ToObject method returns an object, so 81 00:05:44,200 --> 00:05:46,850 we need to cast that to the ComicBook type. 82 00:05:46,850 --> 00:05:50,290 But then we can just access the RowVersion property. 83 00:05:50,290 --> 00:05:53,731 Now that we've given the user the option to choose the client wins option, 84 00:05:53,731 --> 00:05:56,964 let's update the error message to let them know that they can do that. 85 00:05:59,240 --> 00:06:04,784 If you still want to make your changes, 86 00:06:04,784 --> 00:06:09,660 then click the Save button again. 87 00:06:11,040 --> 00:06:15,220 Otherwise click the Cancel button 88 00:06:17,310 --> 00:06:20,800 to discard your changes. 89 00:06:20,800 --> 00:06:23,980 Let's make one more change before we test our changes. 90 00:06:23,980 --> 00:06:27,013 Currently when the Cancel button is clicked, 91 00:06:27,013 --> 00:06:32,221 we send the user to the comic book detail page, but if the entity has been deleted 92 00:06:32,221 --> 00:06:37,060 redirecting the user to that page will result in a 404 not found error. 93 00:06:37,060 --> 00:06:41,733 To resolve this issue we can set a flag on the view model to indicate that the entity 94 00:06:41,733 --> 00:06:42,845 has been deleted. 95 00:06:42,845 --> 00:06:47,016 We can use that flag in the view to determine the correct URL with the cancel 96 00:06:47,016 --> 00:06:47,560 button. 97 00:06:48,980 --> 00:06:49,920 For the flag, 98 00:06:49,920 --> 00:06:55,160 let's add a boolean property to view model named ComicBookHasBeenDeleted. 99 00:06:55,160 --> 00:06:57,808 Then we can set the property to true when we detect 100 00:06:57,808 --> 00:06:59,687 that the entity has been deleted. 101 00:07:01,870 --> 00:07:06,333 Now we can update the cancel button to use our view model flag to conditionally set 102 00:07:06,333 --> 00:07:08,571 the anchored elements href attribute. 103 00:07:08,571 --> 00:07:13,000 If the Comic Book has been deleted, let's set the URL to the list page. 104 00:07:13,000 --> 00:07:17,276 Otherwise, we'll set it to the detail page, which was the existing behavior. 105 00:07:31,804 --> 00:07:34,159 And finally, let's update the error message so 106 00:07:34,159 --> 00:07:37,790 that the user will know what will happen when they click the cancel button. 107 00:07:38,920 --> 00:07:46,884 Click the Cancel button to return to the list page. 108 00:07:46,884 --> 00:07:49,032 And now, lets test our changes. 109 00:07:52,224 --> 00:07:54,294 I'll start by adding a new Comic Book, 110 00:07:54,294 --> 00:07:57,370 so we can test the deleted concurrency use case. 111 00:07:57,370 --> 00:08:02,832 I'll select Bone for the series and set the issue number to 4. 112 00:08:07,020 --> 00:08:10,677 Jeff Smith for the artist, and Script for the role. 113 00:08:13,219 --> 00:08:15,050 And click the Save button. 114 00:08:15,050 --> 00:08:16,810 Let's test the updated use case. 115 00:08:17,820 --> 00:08:20,511 Open the Edit Comic Book page into another tab. 116 00:08:22,662 --> 00:08:24,036 Save the first tab. 117 00:08:25,683 --> 00:08:27,547 Then attempt to save the second tab. 118 00:08:32,155 --> 00:08:35,030 Okay, here's our new error message. 119 00:08:35,030 --> 00:08:38,770 Hovering over the Cancel button show us that we'd browse back to the comic book 120 00:08:38,770 --> 00:08:41,480 detail page if we click the button. 121 00:08:41,480 --> 00:08:44,218 Let's try clicking the Save button. 122 00:08:44,218 --> 00:08:49,120 Great, we were able to save by choosing the client wins option. 123 00:08:49,120 --> 00:08:51,799 Now let's tested deleted use case. 124 00:08:51,799 --> 00:08:55,971 To start let's open a comic book detail page into another tab and 125 00:08:55,971 --> 00:08:59,610 click the edit button to browse to Edit Comic Book page. 126 00:09:01,720 --> 00:09:05,083 Then back in the other tab let's delete the Comic Book. 127 00:09:11,289 --> 00:09:15,960 We're back at the list page and we can see that Bone #4 isn't in the list. 128 00:09:17,030 --> 00:09:19,579 Let's switch to the other tab and try to Save. 129 00:09:23,704 --> 00:09:28,010 And we get the error message letting us know that the comic book has been deleted. 130 00:09:29,720 --> 00:09:33,520 And clicking the Cancel button takes us back to the list page. 131 00:09:33,520 --> 00:09:34,632 Just like we wanted it to. 132 00:09:34,632 --> 00:09:39,000 Next step, we'll see how to handle concurrency when deleting records