For any multiuser transactional system, you must decide how to handle concurrent updates and possible versioning issues. In this recipe, we will show you how to set up versioning and optimistic concurrency with NHibernate.
Versioning
to the MappingRecipes
project.VersionedProduct
to the folder:public class VersionedProduct { public virtual int Id { get; protected set; } public virtual int Version { get; protected set; } public virtual string Name { get; set; }public virtual string Description { get; set; }}
VersionProduct.hbm.xml
:<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MappingRecipes"
namespace="MappingRecipes.Versioning">
<class name="VersionedProduct">
<id name="Id">
<generator class="native"/>
</id>
<version name="Version" />
<property name="Name"/>
<property name="Description"/>
</class>
</hibernate-mapping>
Recipe
to the folder:public class Recipe : HbmMappingRecipe { protected override void AddInitialData(ISession session) { session.Save(new VersionedProduct { Name = "Stuff", Description = "Cool" }); } public override void RunQueries( ISessionFactory sessionFactory) { try { using (var s1 = sessionFactory.OpenSession()) using (var s2 = sessionFactory.OpenSession()) using (var tx1 = s1.BeginTransaction()) using (var tx2 = s2.BeginTransaction()) { var product1 = s1.Get<VersionedProduct>(1); var product2 = s2.Get<VersionedProduct>(1); product1.Name = "Modified in session 1"; product2.Name = "Modified in session 2"; tx1.Commit(); Console.WriteLine("Commit 1"); //This should fail tx2.Commit(); Console.WriteLine("Commit 2"); } } catch (Exception ex) { Console.Error.WriteLine(ex); } } }
Versioning
recipe.Suppose you have a database application with two users, and user #1 and user #2 both pull up the same data on their screens and make changes. User #1 submits her changes to the database and a few moments later user #2 submits his changes. User #2's changes will silently overwrite user #1's changes without any concurrency check. There are two ways to prevent this: optimistic and pessimistic concurrency.
Optimistic concurrency is the process in which 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 but 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 take the following form:
UPDATE Versioned SET Version = 2, Name = 'Modified in session 1' WHERE Id = 1 AND Version = 1
NHibernate checks whether the version is of the same value as when the entity was loaded from the database, and then increments the value. The version field will not be 1 if the entity was already updated and no rows are updated by this statement. NHibernate detects this and throws a StaleStateException
, meaning the entity in memory is stale, or out of sync with the database.
Pessimistic concurrency is the process where a user obtains an exclusive lock on the data while they are editing it. It requires us to have 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 kind of locking with NHibernate, your application must call session.Lock
within a transaction.
In addition to integer version fields, NHibernate allows you to use DateTime
based version fields. However, Micorosoft SQL Server has a DateTime
resolution of about three milliseconds. This might fail when two updates occur almost simultaneously. We can use SQL Server 2008's DateTime2
data type, which has a resolution of 100 nanoseconds, or SQL Server's timestamp data type for the version field.
NHibernate allows you to use the more traditional form of optimistic concurrency through the optimistic-lock
mapping attribute. A simple example would look similar to the following code:
<class name="Product" dynamic-update="true" optimistic-lock="dirty">
In this case, changing a VersionedProduct
name from Stuff
to Junk
would generate SQL, as shown in the following code:
UPDATE VersionedProduct SET Name = 'Junk' WHERE Id = 1 AND Name = 'Stuff'
This ensures that another user does not change the Name
value because this user has already read the value. However, another user may have changed other properties of this entity.
Another alternative is to set optimistic-lock
to all
. In this case, an update would generate SQL similar to the following code:
UPDATE VersionedProduct SET Name = 'Junk' /* @p0 */ WHERE Id = 1 AND Name = 'Stuff' AND Description = 'Cool'
As you might have guessed, in this case, we check the values of all the 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 setting all properties explicitly.
3.137.172.115