Data Concurrency in
Data Concurrency in
NET
When multiple users attempt to modify data at the same time, controls need to be established in
order to prevent one user's modifications from adversely affecting modifications from
simultaneous users. The system of handling what happens in this situation is called concurrency
control.
Pessimistic Concurrency
Pessimistic concurrency is typically used for two reasons. First, in some situations there is high
contention for the same records. The cost of placing locks on the data is less than the cost of
rolling back changes when concurrency conflicts occur.
Pessimistic concurrency is also useful for situations where it is detrimental for the record to
change during the course of a transaction. A good example is an inventory application. Consider a
company representative checking inventory for a potential customer. You typically want to lock
the record until an order is generated, which would generally flag the item with a status of
ordered and remove it from available inventory. If no order is generated, the lock would be
released so that other users checking inventory get an accurate count of available inventory.
However, pessimistic concurrency control is not possible in a disconnected architecture.
Connections are open only long enough to read the data or to update it, so locks cannot be
sustained for long periods. Moreover, an application that holds onto locks for long periods is not
scalable.
Note: If your underlying data source supports transactions, you can simulate pessimistic
concurrency by updating your data within a transaction.
Optimistic Concurrency
In optimistic concurrency, locks are set and held only while the database is being accessed. The
locks prevent other users from attempting to update records at the same instant. The data is
always available except for the exact moment that an update is taking place.
When an update is attempted, the original version of a changed row is compared against the
existing row in the database. If the two are different, the update fails with a concurrency error. It
is up to you at that point to reconcile the two rows, using business logic that you create.
Last in Wins
With "last in wins," no check of the original data is made and the update is simply written to the
database. It is understood that the following scenario can occur:
User A fetches a record from the database.
User B fetches the same record from the database, modifies it, and writes the updated
record back to the database.
User A modifies the 'old' record and writes it back to the database.
In the above scenario, the changes User B made were never seen by User A. Be sure that this
situation is acceptable if you plan to use the "last in wins" approach of concurrency control.
If the date-time stamps or version numbers match, the record in the data store has not changed
and can be safely updated with the new values from the dataset. An error is returned if they don't
match. You can write code to implement this form of concurrency checking in Visual Studio .NET.
You will also have to write code to respond to any update conflicts. To keep the date-time stamp
or version number accurate, you need to set up a trigger on the table to update it when a change
to a row occurs.
Note: Adding new records (the INSERT command) only require the current values since no
original record exists and removing records (the DELETE command) only requires the original
values in order to locate the record to delete.
The following example shows the command text for a dataset command that updates a typical
Customers table. The command is specified for dynamic SQL and optimistic concurrency.
Note that the nine SET statement parameters represent the current values that will be written to
the database, whereas the nine WHERE statement parameters represent the original values that
are used to locate the original record.
The first nine parameters in the SET statement correspond to the first nine parameters in the
parameters collection. These parameters would have their SourceVersion property set to
Current.
The next nine parameters in the WHERE statement are used for optimistic concurrency. These
placeholders would correspond to the next nine parameters in the parameters collection, and each
of these parameters would have their SourceVersion property set to Original.
The SELECT statement is used to refresh the dataset after the update has occurred. It is
generated when you set the Refresh the DataSet option in the Advanced SQL Generations
Options dialog box.
Note: The above SQL uses named parameters, whereas oleDbDataAdapter commands use
question marks (?) as parameter placeholders.
By default Visual Studio will create these parameters for you if you select the Optimistic Currency
option in the DataAdapter Configuration Wizard. It is up to you to add code to handle the errors
based upon your own business requirements. ADO.NET provides a DBConcurrencyException
object that returns the row that violates the concurrency rules. For more information, see
Handling Concurrency Errors.
You will catch the error, and then display the different versions of the record, allowing the user
(you) to determine what should happen with the pending changes made by User 1.
Note: Using a dataset is only one option for data access, and is not the optimal choice in some
scenarios. Nonetheless, datasets are usually the right choice in Windows Forms applications,
and this walkthrough illustrates one scenario in which datasets are an appropriate choice.
Next, you will add code that changes the au_fname column in the DsAuthors1 dataset. The code
then calls the updateDatabase procedure to try to write this change to the database. Because
User 2 changed the value earlier, a concurrency error will be raised.
Note For simplicity's sake, this walkthrough uses the second dataset (DsAuthors2) as the data
source for fetching the current record in the database. In a real-world application you would
require the actual data source to fetch the current value of the record that raised the error.
Tip: To view the generated code, double-click the form and expand the dimmed "Windows Form
Designer generated code" section.
At 1:00 p.m., User1 reads a row from the database with the following values:
To test for an optimistic concurrency violation when updating a row in Table1, you would issue
the following UPDATE statement:
UPDATE Table1 Set Col1 = @NewCol1Value, Set Col2 = @NewCol2Value, Set Col3 =
@NewCol3Value WHERE Col1 = @OldCol1Value AND Col2 = @OldCol2Value AND Col3 =
@OldCol3Value
As long as the original values match the values in the database, the update is performed. If a
value has been modified, the update will not modify the row because the WHERE clause will not
find a match.
Note that it is recommended to always return a unique primary key value in your query.
Otherwise, the preceding UPDATE statement may update more than one row, which might not be
your intent.
If a column at your data source allows nulls, you may need to extend your WHERE clause to
check for a matching null reference in your local table and at the data source. For example, the
following UPDATE statement verifies that a null reference in the local row still matches a null
reference at the data source, or that the value in the local row still matches the value at the data
source.
UPDATE Table1 Set Col1 = @NewVal1 WHERE (@OldVal1 IS NULL AND Col1 IS NULL) OR Col1 =
@OldVal1
You may also choose to apply less restrictive criteria when using an optimistic concurrency model.
For example, using only the primary key columns in the WHERE clause results in the data being
overwritten regardless of whether the other columns have been updated since the last query. You
can also apply a WHERE clause only to specific columns, resulting in data being overwritten unless
particular fields have been updated since they were last queried.
// The Update command checks for optimistic concurrency violations in the WHERE clause.
custDA.UpdateCommand = new SqlCommand("UPDATE Customers (CustomerID, CompanyName)
VALUES(@CustomerID, @CompanyName) WHERE CustomerID = @oldCustomerID AND
CompanyName = @oldCompanyName", nwindConn);
custDA.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID");
custDA.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 30,
"CompanyName");
You can inspect the results of the configuration by examining the three update-related data
adapter command properties: DeleteCommand, InsertCommand, and UpdateCommand.
The following example attempts to update a data source with the contents of myDataset from
within a Try… Catch block, if an error is raised the error message along with the first column of
the offending data row is displayed.
Note The code below is an illustration of one strategy in handling a database update error. The
code assumes several things; an existing connection to a database, an existing dataset named
myDataset, as well as the assumption that execution of the update command will raise a
concurrency violation.