Practical ASP.NET

Handling Concurrency with ObjectDataSource

Peter revisits the problem of dealing with multiple updates to the same data, but this time he looks at a solution that involves the ObjectDataSource.

In earlier columns, I looked at solutions for handling data concurrency (i.e., when two or more users update the same data at the same time). After outlining the problem, I provided two solutions: one to put in your middle-tier business objects and one to put in your SQL statements, either inside your business objects or in the SqlDataSource's update statements.


However, there's also a solution that you can implement in the ObjectDataSource to handle part of the problem, and that's the subject of this column.

ObjectDataSource and the Factory Method Pattern
A quick review: The ObjectDataSource makes it possible for you to bind middle-tier business objects to DataViews, provided that you've implemented the Factory Method pattern (i.e., you have one object with a set of methods, the "factories," that return the objects you want to display in your user interface). In the Factory Method pattern, you also have methods that, when passed a business object, handle making updates to your database.

What the ObjectDataSource allows you to do is check to see which values were changed in the DataView by the user. This allows you to update only those values which were changed. This does not prevent one user from updating a record already updated by another user. However, as I pointed out in an earlier column, if you only update the fields the user has actually changed, you can eliminate most (I would say all) of the problems with concurrency.

The primary issue in concurrency problems is preventing the second user who's doing an update from overwriting changes made by an earlier user's update. In a well-designed database, all values in a record are dependent on the primary key field(s) and otherwise independent of each other. In that scenario, if you only update the fields that your user changes, your concurrency problems go away.

Getting Original Values
By default, the ObjectDataSource passed as single object to your update method that a single object holding the values set in the DataView by the user. To get both the original and update values you need to make two sets of changes: one set to the ObjectDataSource and the second set to your update method.

The fundamental change is to have the ObjectDataSource pass multiple parameters to your update method: one set of parameters that hold the original values that were displayed to the user and a second set of parameters containing the values after the user's updates. In your update method, you'll need to provide a set of parameters with the same names as the properties on the object displayed in the DataView (i.e., if the object returned by your factory method has a property called "LastName," then your update method must have a parameter called "LastName"). You don't need to provide a parameter for every property on your object -- only the properties that are actually displayed in the DataView.

To get the ObjectDataSource to pass a set of parameters with the original values you must set the ObjectDataSource's ConflictDetection property to CompareAllValues. But now that you're getting two sets of parameters, you also need to control the name of the second set of parameters containing the original values. Those names are controlled through a format string that you place in the ObjectDataSource's OldValuesParameterFormatString. If you set the property to "original_{0}" then the parameters in your update method that accept the "original" values must have the string "original_" prefixed to their name. And do pick "original_{0}" as your format string because almost any change to the ObjectDataSource seems to cause the ObjectDataSource to set that value in OldValuseParameterFormatString.

Assuming that my DataView is displaying the EmployeeId, LastName and Region properties from my object, my update method would look like the following code. In this update method, I compare the original and current values and, when the values are different, I create a SQL statement that updates only those fields. The right way to do that is with ADO.NET parameters, but since that's an ADO.NET issue and not an ASP.NET issue, I'll just concatenate strings here to make what I'm doing obvious:

Public Shared Sub UpdateSalesPersonWithConflict( _
     ByVal EmployeeId As String, _
     ByVal LastName As String, _
     ByVal Region As String, _
     ByVal Original_EmployeeId As String, _
     ByVal Original_LastName As String, _
     ByVal Original_Region As String)

Dim sbSQL As New StringBuilder
sbSQL.Append("Update SalesPerson Set ")

If LastName <> Original_LastName Then
     sbSQL.Append("LastName = '" & LastName & "' ,")
End If
If Region <> Original_Region Then
          sbSQL.Append("Region = '" & Region & "' ,")
End If
sbSQL = sbSQL.Remove(sbSQL.Length, 1)
sbSQL.Append(" Where EmployeeId = '" & EmployeeId & "';")

UpdateDatabase(sbSQL.ToString)

End Sub


About the Author

Peter Vogel is a principal in PH&V Information Services, specializing in ASP.NET development with expertise in SOA, XML, database, and user interface design. His most recent book ("rtfm*") is on writing effective user manuals, and his blog on technical writing can be found at rtfmphvis.blogspot.com.

Reader Comments:

Sat, Oct 24, 2009 Peter Vogel Canada

This is an omnibus response to a several of the comments to this article: 1) Identifying which fields are independent: If your database is in third normal form then all the fields should be dependent on the key and only on the key: There shouldn't be any relationships between fields in the row (my issue with fields "that are related but 'not really'" is just a less technical way of making the same point. 2) Using optimistic concurrency: An excellent strategy and one that I've discussed in other columns. My concern is that the user is forced to review and re-make changes just because another user changes some other fields. My claim is "Who cares as long as only the columns that I change are updated?" 3) The issue about the user making a decision on a view of the record that has now been changed is a good point--it's just that I don't think that it's a problem that crops up with many tables. I'd rather handle it as a special case when necessary than make it the default behaviour for all of my updates. 4) No sure what is meant by identifying the "intended" use of the data--perhaps, display vs. update vs. delete?

Sat, Oct 24, 2009 Peter Vogel Canada

Thanigainathan: I'm not sure that I understand your question: Which one is "the other one" you mean? I'm suggesting that you only update the fields that have been changed. If "the other one" are the fields the user hasn't changed then you don't need to update those fields.

Thu, Jul 2, 2009 Thanigainathan Chennai-India

Hi,

You are article is very nice. I have a question.

So you mean to say that if we update the records that are only updated this concurrency problem goes away. What about the other one. Should we let them go the same way ?

Thanks,
Thani

Thu, May 28, 2009 Peter Vogel Canada

Actually, the example of city and street plays right into the point I made in the earlier column. For there to be a problem there must be two fields on the row that are related to each other but there be scenarios when the user would change one and not the other--the two fields are related but "not really." It seems to me that if a user changed the city they would also be changing the street. Adjusting the value (calculating the onhand value by subtracting the amount sold) is an excellent strategy but, unfortunately, limited in scope.

Wed, May 27, 2009 Mitch

This is only effective if you build the update logic into the data layer itself. It must be able to identify what fields are truly independent (such as city and street). Furthermore, requests to the DL should identify their intented use of the data.

Tue, May 26, 2009

I do not like this approach since the second user may have made their updates based on a view of the data that did not include the first users updates. You do not have control of what version of the object each user has when they perform the update. The best approach is optimistic locking based on a timestame. If the object has been updated, show the user an updated version and let them decide if their update is appropriate.

Tue, May 26, 2009 Pete Skaarup United States

"However, as I pointed out in an earlier column, if you only update the fields the user has actually changed, you can eliminate most (I would say all) of the problems with concurrency.
" If user2 changed city, then I can change street?

Tue, May 26, 2009

I would check the lenght of sbSQL before the UpdateDatabase to make sure something changed. This technique is also nice with quantity fields because the update can be for the net change. For example, a Qty_On_Hand adjusted by the use from 15 to 13 would yield an SQL statement adjusting Qty_On_Hand by -2. Thus if someone else is posting a stock receipt, their stock receipt is not lost.

Add Your Comments Now:

Your Name:(optional)
Your Email:(optional)
Your Location:(optional)
Comment:
Please type the letters/numbers you see above