ComponentOne DataObjects for .NET
Handling Concurrency Conflicts
DataObjects for .NET (Enterprise Edition) > Updating the Database > Handling Concurrency Conflicts

Updating a row can fail because another user has changed the same row between the time the row was fetched and the time it is updated. Allowing concurrent changes to rows with "first tried – first succeeded" policy is usually called optimistic concurrency. When updating a row, your application must be sure that it is actually updating the same row as it had originally fetched from the database. In different application scenarios, the notion of "the same row" can be different. In some cases, you may need all the fields in the row in the database to remain intact from the moment the row was fetched till the moment it is updated. In other cases, it is enough that an ID, a primary key field remains intact, and all other fields can be allowed to be freely modified by various users on the "first tried – first succeeded" basis. To set this concurrency control policy, use the UpdateLocateMode and UpdateLocate properties. They control the collection of fields used in the WHERE clause of the action SQL statements to locate the record for update. A row passes the concurrency check if the WHERE clause finds a row in the database for update. Including more fields in the WHERE clause (manipulating the UpdateLocateMode and UpdateLocate properties) makes the concurrency check more strict: changes made by other users to one of these fields fail the update. Excluding fields from the WHERE clause makes the check less strict, making it more tolerant to changes made by other users.

When a concurrency conflict occurs, it fires the C1TableLogic.AfterUpdateRow event with its SqlStatus argument set to ConcurrencyConflicts. At this point, developers have an opportunity to reconcile this conflict directly on the server. Business logic does not always allow you to reconcile conflicts on the server, because this must be done without user interface and without asking for user choices. Still, if that is possible from the business point of view, it is usually preferable. To reconcile a conflict in the AfterUpdateRow event, do whatever needs to be done to update the row (remember that default update by the generated SQL statement has not been done because of the conflict), change the field values in the Row argument to reflect possible changes to the fields due to conflict resolution, and set the SqlStatus argument to Succeeded.

When the C1TableLogic.AfterUpdateRow fires on a conflicting row, its Status argument is set to Continue. This means that the conflict will not be regarded as an exceptional situation, a fatal failure, that DataObjects for .NET will continue processing other modified rows after that. However, if you need to abort processing at this point, you can do so by setting the Status argument to SkipAllRemainingRows or to ErrorsOccurred. The latter also has the additional effect of raising an exception on the client.

Conflicts that are not resolved on the server are passed to the client and have to be reconciled there, see Handling Update Errors on the Client. Concurrency conflicts do not raise an exception on the client, but fire the UpdateError event. Rows that caused conflicts can be found in the C1DataSet using the properties and methods RowError, HasErrors, HasErrors and GetErrors.

Note that this distinction between client and server is not applicable to a direct client application (see Application Configurations for the description of DataObjects for .NET client and server), that is a 2-tier application updating the database directly from the client, an application where C1SchemaDef and C1DataSet components reside on the same design surface. In this case, the client and the server are one and the same application so you can reconcile conflicts in the AfterUpdateRow event with user interface, see Handling Update Errors on the Client.