NET - Part I
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.
There are three common techniques for managing what happens when users try to modify the same data at
the same time: pessimistic, optimistic, and last-in wins.
• Pessimistic concurrency control - a row is unavailable to users from the time the record is
fetched until it is updated in the database.
• Optimistic concurrency control - a row is unavailable to other users only while the data is
actually being updated. The update examines the row in the database and determines whether
any changes have been made. Attempting to update a record that has already been changed,
results in a concurrency violation.
• "Last in wins" - a row is unavailable to other users only while the data is actually being
updated. However, no effort is made to compare updates against the original record; the record is
simply written out, potentially overwriting any changes made by other users since you last
refreshed the records.
There are two ways to handle the issues with optimistic concurrency. They are:
In the version number approach, the record to be updated must have a column that
contains a date-time stamp or version number. The date-time stamp or a version
number is saved on the client when the record is read. This value is then made part
of the update.
One way to handle concurrency is to update only if value in the WHERE clause
matches the value on the record. The SQL representation of this approach is:
UPDATE Table1 SET Column1 = @newvalue1, Column2 = @newvalue2
WHERE DateTimeStamp = @origDateTimeStamp
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.
Each data adapter command has a parameters collection for each of its four
commands (DELETE, INSERT, SELECT, and UPDATE). Each command has
parameters for both the original values, as well as the current (or modified) values.
Note Adding new records (the INSERT command) only requires 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.
UPDATE Customers
SET CustomerID = @currCustomerID,
CompanyName = @currCompanyName,
ContactName = @currContactName,
ContactTitle = currContactTitle,
Address = @currAddress,
City = @currCity,
PostalCode = @currPostalCode,
Phone = @currPhone,
Fax = @currFax
WHERE (CustomerID = @origCustomerID) AND (Address = @origAddress OR
@origAddress IS NULL AND Address IS NULL) AND (City = @origCity OR @origCity
IS NULL AND City IS NULL)
AND (CompanyName = @origCompanyName OR @origCompanyName IS NULL AND
CompanyName IS NULL) AND (ContactName = @origContactName OR
@origContactName IS NULL AND ContactName IS NULL) AND (ContactTitle =
@origContactTitle OR @origContactTitle IS NULL AND ContactTitle IS NULL)
AND (Fax = @origFax OR @origFax IS NULL AND Fax IS NULL) AND (Phone =
@origPhone OR @origPhone IS NULL AND Phone IS NULL) AND (PostalCode =
@origPostalCode OR @origPostalCode IS NULL AND PostalCode IS NULL);
SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City,
PostalCode, Phone, Fax
FROM Customers WHERE (CustomerID = @currCustomerID)
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.
By default Visual Studio will create these parameters for you if you select the
Optimistic Concurrency option in the DataAdapter Configuration Wizard. It is up to
you to add code to handle the errors based upon your own business requirements.
When a row of data is read by an application and presented to the user for possible modification, most applications
do not hold a lock on the row. That is good because if they did hold locks while the user contemplated making changes,
many lockout situations would occur. If fact, unless the application used the Read Uncommitted isolation level, other
users could not even look at the data until the lock is released.
But if there are no locks held, how does one prevent incorrect successive updates to the same row? Let's say that a
shipping application is updating the UnitsInStock column for the Products table. What we want to prevent is this
sequence of updates:
The problem with allowing A's attempted update to succeed is that anything that B changed is ignored. B reduced the
inventory level by 5 to 1. A's change to UnitsInStock column should not be allowed in this situation. Not only is A's
change incorrect but I probably should not have been allowed at all because the total units shipped would be greater
than the inventory.
The answer to preventing this problem lies in the WHERE clause of the UPDATE statement. As we have seen, the
WHERE clause is used to identify the primary key to the row. It can also be used to identify the data that the client
application thinks that it is updating. If the row has been changed since the client application read the row, it should not
be allowed to apply updates. This is accomplished in one of two ways:
• A clause specifying the original value of every column is added to the WHERE clause
• A check on the timestamp column is added to the WHERE clause.
Many systems, including ADO, will write the check for the original value of every column. That technique works in
every database management system, not just SQL Server. SQL Server offers the timestamp data type, which can be used
to track when a row changes. Every time a row is updated, a timestamp (a.k.a rowversion) is updated to a new unique
value. This simplifies writing the stored procedures because we only have to pass in the timestamp, not the original
value of every column.
While in a disconnected architecture the "Pessimistic concurrency control" can not be implemented using
database locks, it can be implemented as follows:
• Using lock-bits at the table row level and maintain them in a session pool.
• Clear the lock-bits when the user leaves the page under program control. This has to be done to
free the locks as soon as possible, but there is no guarantee that this occurs.
• As the user can leave the browser or the site at any moment, we have to clear the lock-bits
stored is the session pool on Session_End.
• And as a final countermeasure, we might have a demon running on the server cleaning old locks.
We have to keep in mind that an application that holds locks for long periods is not scalable, but this
concurrency control schema might have to be implemented on some portions of the application.
"Last in wins"
To implement this concurrency control, we do not have to do anything. But this might be unacceptable in
some circumstances if different users start to access the same records frequently.
As explained at MSDN:
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.
This article focuses on this approach and I will go deeply through it.
The optimistic concurrency control is based on a Record Version. Several users can open a page for the
same record, but only the first one to save it will succeed.
How it works
• User A fetches a record from the database along with its version, more on this later.
• User B fetches the same record from the database, also with its version and writes the updated
record back to the database, updating the version number.
• User A "tries" to write the record to the database, but as the version on the database is different
from the version hold by user A, the write fails, leaving user B's changes intact.
• The GUID is not portable if we want to port the code to other platforms.
• The GUID uses 40 bytes instead of the 8 bytes used by the date.
• The date-time tell us when the record was last updated.
If the concurrency value given by the user does not match the concurrency value in the database, the record
will not be updated and 0 rows affected will be returned.
With this SELECT we have to consider that, while a user was modifying the record, another user might
have deleted the same record. In this case, the rows affected will also be 0.
If you want to give the user an accurate message about the situation, we can not tell that the record was
updated by another user if it was deleted. To solve this, we have to check if a record with the primary key
exists in the table.
SELECT COUNT(*) FROM table WHERE pk = pk_value
At this point, if the record count is zero, the record was deleted, other wise; a concurrency exception has to
be thrown.