Handling versioning and concurrency

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.

Getting ready

Complete the Getting ready instructions at the beginning of this chapter.

How to do it…

  1. Add a new folder named Versioning to the MappingRecipes project.
  2. Add a new class named 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; }}
  3. Add an embedded mapping named 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>
  4. Add a new class named 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);
      }
     }
    }
  5. Run the application and start the Versioning recipe.
  6. Investigate the output and the query log.
    How to do it…

How it works…

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.

There's more…

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.

See also

  • Mapping a class with XML
  • Creating class hierarchy mappings
  • Mapping a one-to-many relationship
  • Setting up a base entity class
  • Creating mappings fluently
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.172.115