For any multiuser transactional system, you must decide between optimistic and pessimistic concurrency to handle concurrent updates and versioning issues. In this recipe, I'll show you how to properly set up versioning and optimistic concurrency with NHibernate.
Entity
base class, add a Version
property, as shown in the following code:public abstract class Entity<TId>
{
public virtual TId Id { get; protected set; }
protected virtual int Version { get; set; }
public override bool Equals(object obj)
{
return Equals(obj as Entity<TId>);
}
Product
mapping, add the version element as shown in the following code:<natural-id mutable="true">
<property name="Name" not-null="true" />
</natural-id>
<version name="Version" />
<property name="Description" />
<property name="UnitPrice" not-null="true" />
ActorRole
mapping, add the version element shown here:<id name="Id">
<generator class="guid.comb" />
</id>
<version name="Version" />
<property name="Actor" not-null="true" />
<property name="Role" not-null="true" />
Suppose you have a database application with two users. User #1 and user #2 both pull up the same data on their screen and begin making changes. User #1 submits her changes back to the database. A few moments later, user #2 submits his changes. Without any concurrency checking, user #2's changes will silently overwrite user #1's changes. There are two possible ways to prevent this: optimistic and pessimistic concurrency.
Optimistic concurrency is the process where data is checked for changes before any update is executed. In this scenario, user #1 and user #2 both begin their changes. User #1 submits her changes. When user #2 submits his changes, his update will fail because the current data (after user #1's changes) doesn't match the data that user #2 originally read from the database.
In the example shown here, we use the version field to track changes to an entity. Update statements takes the following form:
UPDATE Product SET Version = 2 /* @p0 */, Name = 'Junk' /* @p1 */, Description = 'Cool' /* @p2 */, UnitPrice = 100 /* @p3 */ WHERE Id = '764de11e-1fd0-491e-8158-9db8015f9be5' /* @p4 */ AND Version = 1 /* @p5 */
NHibernate checks that the version is the same value as when the entity was loaded from the database, and then increments the value. If the entity was already updated, the version field will not be 1, and no rows will be updated by this statement. NHibernate detects the zero rows affected and throws a StaleStateException
, meaning the entity in memory is stale, or out of sync with the database.
The alternative to optimistic concurrency is pessimistic locking. Pessimistic locking is the process where a user obtains an exclusive lock on the data while they are editing it. It takes the pessimistic view that, given the chance, user #2 will overwrite user #1's changes, so it's best not to let user #2 even look at the data. In this scenario, once user #1 pulls up the data, she has an exclusive lock. User #2 will not be able to read that data. His query will wait until user #1 drops the lock or the query times out. Inevitably, user #1 will take a phone call or step away for a cup of coffee while user #2 waits for access to the data. To implement this type of locking with NHibernate, your application must call session.Lock
within a transaction.
In addition to integer version fields, NHibernate also allows you to use DateTime
-based version fields. However, Micorosoft SQL Server has a datetime
resolution of about three milliseconds. This may fail when two updates occur almost simultaneously. It's also possible to use SQL Server 2008's DateTime2
data type, which has a resolution of 100 nanoseconds, or even SQL Server's timestamp
data type for the version field.
NHibernate allows you to use the more traditional form of optimistic concurrency through the mapping attribute optimistic-lock
. A simple example would look like the following code:
<class name="Product" dynamic-update="true" optimistic-lock="dirty">
In this case, changing a Product
name from Stuff
to Junk
would generate SQL as shown in the following code:
UPDATE Product SET Name = 'Junk' /* @p0 */ WHERE Id = '741bd189-78b5-400c-97bd-9db80159ef79' /* @p1 */ AND Name = 'Stuff' /* @p2 */
This ensures that the Name
value hasn't been changed by another user because this user read the value. Another user may have changed other properties of this entity.
Another alternative is to set optimistic-lock
to all
. In this case, a Product
update would generate SQL like this:
UPDATE Product SET Name = 'Junk' /* @p0 */ WHERE Id = 'd3458d6e-fa28-4dcb-9130-9db8015cc5bb' /* @p1 */ AND Name = 'Stuff' /* @p2 */ AND Description = 'Cool' /* @p3 */ AND UnitPrice = 100 /* @p4 */
As you might have guessed, in this case, we check the values of all properties.
When optimistic-lock
is set to dirty
, dynamic-update
must be true
. Dynamic update simply means that the update statement only updates dirty properties, or properties with changed values, instead of explicitly setting all properties.