70-561 - Optimistic concurrency in LINQ to SQL

Filed under: , , , by:

Optimistic concurrency control (OCC) is a concurrency control method that assumes that multiple transactions can complete without affecting each other, and that therefore transactions can proceed without locking the data resources that they affect. Before committing, each transaction verifies that no other transaction has modified its data. If the check reveals conflicting modifications, the committing transaction either rolls back or user manually resolves the conflict by deciding if what changes should be discarded and what changes should be kept. It seems that all production systems should use transactions and have some sort of concurrency control mechanism implemented. However, I have seen a few systems in productions (even fairly recent) that did not have any concurrency control, which was causing a lot of troubles only because someone decided that 2 people would not use the same data, which the assumption, sooner or later, is almost always proven wrong and cost of implementing a concurrency control first place would've been always lower than the cost of fixing the problems in production. Moreover, implementing OCC is very straightforward and really shouldn't take much time. That's why I decided to demonstrate how one can take advantage of optimistic concurrency in LINQ to SQL.
LINQ to SQL has a built-in detection mechanism of optimistic concurrency. When submitting changes, ChangeConflictException is thrown when a conflict has been detected and each conflict can be resolved using Resolve method in ObjectChangeConflict class. Below is a very simple example that uses LINQ to SQL to map Address table in AdventureWorksLT database, but any database and any tables can be used.

In the example user can update 3 fields (for simplicity) and in order to simulate concurrency, the application will be executed twice so both clients will start with the same data (Address record with ID=1000) and then one of the clients will commit its changes and next the other one will commit its, which should create a change conflict as original data got changed by the first client. Next, user will be presented 3 different options to resolve the conflict.
[Address.cs]

[AdventureWorksLTDataContext.cs]

[Program.cs]

The main logic of resolving the concurrency conflict has been implemented within partial class AdventureWorksLTDataContext, which class is the LINQ to SQL DataContext class, in Save method that calls SubmitChanges method to submit the changes and also checks for ChangeConflictException and provides the code to resolve the conflict. An important thing about Save method is that it contains a recursive call, which is required because after user has chosen the mode of resolving each conflict, the changes must be submitted again to the database, which can again cause the concurrency conflict. Recursion could also be replaced with do ... while loop. Also I took advantage of the fact that data context class in LINQ to SQL is a partial class so Save method that allows user resolve the concurrency conflict I added to the partial class I created with the same name as my data context, but I could use extension method or other helper class.
Below are the results of resolving the conflicts using all three different refresh modes, of which understanding is required to properly resolve the conflict.
So for example, both users start with the same address (AddressLine1=Line1, AddressLine2=Line2, City=City) and User2 made changes in AddressLine2 and City (Line2 by User 2 and City by User 2 ) and commits his changes. Next, User 1 tries to submit his changes, which touch AddressLine1 and City (Line 1 by User 1 and City by user 1). Each of them made changes in 2 properties in the Address entity - 1 different (AddressLine1 and AddressLine2) and 1 the same (City). During submitting changes of User1, a concurrency exception is thrown and as depicted by the chart if user selects RefreshModel.KeepChanges, Address entity will contain all changes of User 1 plus the changes of User 2 that were not overwritten by User 1, which means changes in AddressLine2.
As one can see, implementing optimistic concurrency in LINQ to SQL is fairly simple and shouldn't cost a developer a lot of time and there's definitely a lot of benefits of being able not only to detect a concurrency conflict but also to be able to provide a user a mechanism to resolve it in the most adequate fashion in oder to fully meet business requirements regarding change conflicts. I think everyone agrees that every application should be implemented to be able to resolve concurrency conflicts and maybe not only by showing an error message of a change conflict and refreshing data (and aborting some of the changes made by a user) but also by providing a granular mechanism to present a user the changes that happened in the meantime and prompt how to resolve each change. I think I will never forget one of the applications that did not have a concurrency mechanism implemented and a person who claimed that he entered some data to the system (it was even confirmed with the message "Update successful") but they were gone because someone else made his changes in the meantime and as a result no records were updated, but that was never detected and never presented to the user. I know only one - it took a while to find that bug and even more to implement it in that enterprise application (hundreds of thousands lines of code). So any developer that cares about Quality of Service should remember that simple and small things like that make your application robust and make you a thriving developer.

0 comments: