In my earlier articles I showed how we can handle the concurrency using UpdateCheck. In this articles I will show how to deal with Concurrency Exceptions.
In using the Always or WhenChanged options for UpdateCheck, it is inevitable that two users will modify the same values and cause conflicts. In those cases, the DataContext will raise a ChangeConflictException when the second user issues an SubmitChanges request. Because of the likelihood of running into an exception, we need to make sure we wrap the updates inside a structured exception handling block.
Once an exception is thrown, several options to resolve the exception exist. The DataContext helps discover not only the objects that are in conflict, but also which properties are different between the original value, the changed value, and the current value in the database. In order to provide this level of information, we can specify the “RefreshMode” to determine whether the conflicting record is first refreshed from the database to determine the current values. Once we have the refreshed values, we can determine whether we want to retain the original values, the current database values, or our new values. If we want to take the last option and make sure our values are the ones that are retained, we resolve the change conflicts of the context object specifying that we want to keep the changes as shown in the code below.
If we use the RefreshMode.KeepChanges option, we don’t need to inspect the changed values. We are assuming that our values are correct and go ahead and force them into the appropriate row. This can be potentially dangerous. Columns that we didn’t update will be refreshed from the current value in the database. If the business needs demand it, we could merge the changes with the new values from the database; simply set the RefreshMode as RefreshMode.KeepCurrentValues. This way, we’ll incorporate the other user’s changes into our record and add our changes. However, if both users changed the same column, the new value will overwrite the value that the first user updated. To be safe, we can overwrite the values that the second user tried to change with the current values from the database. In that case, use RefreshMode.OverwriteCurrentValues At this point, it would not be beneficial to submit the changes back to the database again, as there would be no difference between the current object and the values in the database. We would present the refreshed record to the user and have them make the appropriate changes again.Depending on the number of changes that the user made, they may not appreciate having to reenter their data. Since SubmitChanges can update multiple records in a batch, the number of changes could be significant. To assist with this, the SubmitChanges method takes an overloaded value to indicate how we wish to proceed when a record is in conflict. We can either stop evaluating further records or collect a listing of objects that were conflicted.
With the ContinueOnConflict option, we’ll need to iterate over the conflicting options and resolve them using the appropriate RefreshMode. With this method, we can at least submit some of the values and then prompt the user to reenter his information in the conflicting items. This could still cause some user resentment, as he would need to review all of the changes to see what records need to be changed.A better solution would be to present the user with the records and fields that were changed. LINQ to SQL not only allows access to this information, but also supports the ability to view the current value, original value, and database value for the conflicting object. The below code demonstrates using the ChangeConflicts collection of the DataContext to collect the details of each conflict.
Each item in the ChangeConflicts collection contains the object that conflicted as well as a MemberConflicts collection. This collection contains information about the Member, CurrentValue, DatabaseValue, and OriginalValue. Once we have this information, we can display it to the user in whatever method we choose. Using this code, we can display details of the concurrency errors that the user creates.In designing systems that allow for multiple concurrent users, we need to consider how to handle concurrency concerns. By catching the exception, we can either handle it using one of the resolution modes or roll the entire transaction back.