Chapter 11. A Whirlwind Tour of Other NHibernate Features

In this last chapter, we are going to look at some of the important NHibernate features that we do not use on a daily basis. You may not find all of these features to be useful everywhere. So you might want to use your best judgement to choose appropriate features based on your requirements.

We would begin the chapter by talking about concurrency control, how it is important, and what NHibernate offers out of the box to deal with concurrency issues.

We would then touch upon event system of NHibernate and find out how to listen to different events and plug in our own logic when a particular event is about to happen or has happened. We would also look at a practical example that utilizes the event system of NHibernate to simplify a cumbersome and repetitive business requirement.

Next feature we would look into is different levels of support for caching available in NHibernate. We would try to go a bit deep into different types of caches to understand why there are multiple cache implementations and learn how to choose the right caching strategy.

NHibernate sessions are usually quite fast, but internally a lot goes on when even a simple entity is persisted to or loaded from the database. These internal operations make use of system resources such as memory and CPU. If you try to load thousands of entities from the database, then the response from the session may get slowed down due to the high usage of system resources. You may even run out of application memory. That is where the lightweight sibling of the session called the stateless session comes in handy. Stateless sessions are light and fast version of the session, which is implemented by turning off some features that we would be happy to use otherwise.

We would close the chapter by talking about user defined types. User defined types is another tool provided by NHibernate for greater control over how data is converted from database types to .NET types, and vice versa.

Concurrency control

At the heart of it, concurrency control is about working out which of the two concurrent database operations win. NHibernate's concurrency control goes beyond that and offers prevention against mistakenly updating stale data. This mostly applies to update operations. Select, insert, and delete operations can happen concurrently without any problem. Before we look into this feature, let's take a moment to understand what kind of problems we can face in absence of concurrency control for update operations. Let's assume that our employee benefits management system has a feature where in HR staff can update personal details of the employees. In a rare situation, it may happen that two HR staff members open the personal details of the same employee and try to update two different fields on their profile. Let's say that the first staff member updates the employee's last name, whereas the second staff member updates the employee's mobile number. Following is the sequence of events as it happens:

  1. Staff 1 opens the personal details page of employee A
  2. Staff 2 opens the personal details page of employee A
  3. Staff 1 updates the last name of employee A
  4. Staff 2 updates the mobile number of employee A

In Chapter 5, Let's Store Some Data into the Database, we learned about the dynamic-update setting which controls whether NHibernate generates the update statement for all properties on the entity or only the changed property. If we assume that dynamic-update is turned off, then the update made by staff 2 in the previous example would overwrite the last name updated by staff 1. When staff 2 opened the personal details page of employee A, she/he was working on the latest data. But by the time staff 1 updated the last name of employee A, that data had become stale. Effectively, Staff 2 updated stale data. You could argue that if we turn on dynamic updates then this problem would go away. Yes it would, as long as the second update does not involve any property that was modified in the first update. If staff 2 had updated the last name and mobile number of employee A, then changes made to the last name by staff 1 would have been overwritten even if dynamic update was on. Working with stale data is almost always dangerous.

Concurrency control features in NHibernate provide ways to detect if we are working with stale data and abort the update operation if that is the case.

There are two types of concurrency controls that NHibernate offers:

  • Optimistic concurrency control: If the database record that is being updated is being changed by some other transaction, then do not proceed with the current update
  • Pessimistic concurrency control: This involves locking the database record that is to be updated so that any other transaction cannot modify the record in the meantime

Let's take a look at how to work with both types of concurrency controls in NHibernate.

Optimistic concurrency control

Optimistic concurrency control boils down to validating that the record that is being updated is not modified by other transaction since it was loaded by the current transaction. NHibernate provides two different ways of performing this validation. First one is using a setting called optimistic-lock and second is using a special column called version to track the version of each record as it is updated by different transactions.

Using optimistic-lock

Optimistic-lock is a setting that can be applied on an entity during its mapping. As of version 4.0 of NHibernate, this setting can only be applied through XML mapping. Support for mapping by code is still being built and is expected to be out in version 4.1. Optimistic-lock takes two values – Dirty and All. Let's jump into an example to see what these mean. Following is a partial XML mapping for Employee that shows how to specify the optimistic-lock setting:

<class name="Employee" 
optimistic-lock="dirty"
dynamic-update="true">

Note that besides setting the optimistic-lock attribute, we have also set the dynamic-update attribute to true. For optimistic locks to work, it is required to turn on the dynamic updates. With the preceding mapping in place, let's say we execute the following code:

using (var tx = Database.Session.BeginTransaction())
{
  var emp = Database.Session.Get<Employee>(id);
  emp.Firstname = "Hillary";
  emp.Lastname = "Gamble";
  tx.Commit();
}

In the preceding code, we are updating Firstname and Lastname on the Employee instance. Following SQL is generated by the preceding code:

UPDATE
  Employee
    SET
      Firstname = @p0,
      Lastname = @p1
    WHERE
      Id = @p2 
      AND Firstname = @p3
      AND Lastname = @p4;
      @p0 = 'Hillary', @p1 = 'Gamble', @p2 = 10649600, @p3 = 'John', @p4 = 'Smith'

The preceding update statement has used an additional WHERE clause to match the Firstname and Lastname column values of the record being updated to the original values that were loaded from the database. If some other database transaction has changed those values, then our update would not succeed and a NHibernate.StaleObjectException would be thrown. Also note that we have turned on the dynamic-updates. It makes sense to use dirty setting of optimistic-lock along with dynamic-updates. You are only validating and updating what has changed and not everything.

If you would rather validate all the columns of the entity instead of just the ones that are changed, then you can use the optimistic-lock setting to all, as shown next:

<class name="Employee" optimistic-lock="all"
dynamic-update="true">

Using this setting, the same previous code would generate the following SQL:

UPDATE
    Employee
  SET
    Firstname = @p0,
    Lastname = @p1
  WHERE
    Id = @p2
    AND EmployeeNumber is null
    AND Firstname = @p3 
    AND Lastname = @p4 
    AND EmailAddress is null
        AND DateOfBirth = @p5
        AND DateOfJoining = @p6
        AND IsAdmin = @p7
        AND Password is null;
    @p0 = 'Hillary', @p1 = 'Gamble', @p2 = 10682368, @p3 = 'John', @p4 = 'Smith', @p5 = 05/03/1972 00:00:00, @p6 = 28/05/2001 00:00:00, @p7 = False

Using the version property

Another way to use optimistic concurrency control is to use a Version property on the entity which mapped using a special mapping construct. The Version property could be something simple similar to an integer, as shown in the following code snippet:

public class Employee : EntityBase<Employee>
{
  //other properties on Employee class

  public virtual int Version { get; set; }
}

And following is how we map this Version property:

public class EmployeeMappings : ClassMapping<Employee>
{
  public EmployeeMappings()
  {
    Version(e => e.Version, versionMapper =>
    {
      versionMapper.Generated(VersionGeneration.Never);
    });
  }
}

We have got a special method Version that takes in two lambda expressions as its input. First one expects a lambda to the property that is to be used for version tracking. Second lambda expression uses an instance of NHibernate.Mapping.ByCode.IVersionMapper to further configure the mapping of Version. There are several mapping options available here which I would like to leave to you to explore further. The only one that I have used and which is important is the IVersionMapper.Generated method. It is used to tell NHibernate whether we are expecting the database to generate the value for the version column or should NHibernate itself generate that value. This is specified by using enumeration NHibernate.Mapping.ByCode.VersionGeneration. A value of Never means that the database never generates this value and NHibernate should generate it instead.

Another value Always is used to tell NHibernate that it can leave the version generation to the database.

With this mapping in place, when we save a transient Employee instance, the following SQL is generated:

INSERT INTO Employee 
            ( 
                        Version, 
                        EmployeeNumber, 
                        Firstname, 
                        Lastname, 
                        EmailAddress, 
                        DateOfBirth, 
                        DateOfJoining, 
                        IsAdmin, 
                        Password, 
                        Id 
            ) 
            VALUES 
            ( 
                        @p0, 
                        @p1, 
                        @p2, 
                        @p3, 
                        @p4, 
                        @p5, 
                        @p6, 
                        @p7, 
                        @p8, 
                        @p9 
            );
@p0 = 1, @p1 = NULL, @p2 = 'John', @p3 = 'Smith', @p4 = NULL, 
@p5 = 01/01/0001 00:00:00, @p6 = 01/01/0001 00:00:00, 
@p7 = false, @p8 = NULL, @p9 = 11

Note that NHibernate has automatically generated and inserted a value for the Version column. If the preceding record is updated, then NHibernate would generate the following SQL:

UPDATE Employee
SET Version = @p0,
  Firstname = @p1,
  Lastname = @p2
WHERE Id = @p3
AND Version = @p4;@p0 = 2, @p1 = 'Hillary',
@p2 = 'Gamble', @p3 = 11, @p4 = 1

When this record was loaded from the database, Version had a value of 1. The Update statement generated by NHibernate increments that value to 2. At the same time, a check is being made that the version column in the database still has a value of 1. If some other transaction had updated the same record in the meantime, then the Version column would have had a different (incremented) value and the update statement would have failed, resulting in a NHibernate.StaleObjectException. When this update succeeds, then any other transaction that holds stale record would fail with NHibernate.StaleObjectException.

Using a numeric value for version tracking works nicely. But if there are database inserts happening that are not going to through NHibernate then you need to make sure that version is correctly inserted and updated. A database such as MS SQL offers a special column of type ROWVERSION which is automatically generated every time a new record is inserted and updated when the record is updated. You can make use of this feature of MS SQL Server and extend your concurrency control beyond NHibernate. NHibernate supports this native database feature via its user defined version type feature.

Note

User-defined version type is an extension of user-defined types feature that we would cover in this chapter.

In order to make use of MS SQL's timestamp-based versioning of the database records, we would need to declare a version property of type byte[], shown as follows:

public virtual byte[] RowVersion { get; set; }

We then map this property as a version property, as shown in the following code snippet:

Version(e => e.RowVersion, versionMapper =>
{
  versionMapper.Generated(VersionGeneration.Always);
  versionMapper.UnsavedValue(null);
  versionMapper.Type<BinaryTimestamp>();
  versionMapper.Column(v =>
  {
    v.SqlType("timestamp");
    v.NotNullable(false);
    });
  });

We are telling NHibernate that versions are always generated by database. We are also telling NHibernate that the database type of the version column on database side is timestamp. The most important bit is the declaration of BinaryTimestamp as the type to be used for conversion from database's timestamp value to byte[] used in code. You can find the code for the BinaryTimestamp class in the accompanying code for this chapter.

Pessimistic concurrency control

Pessimistic locking works on the principle of "whoever gets there first, gets a chance to update the record". This is achieved by using row-level locks offered by most leading RDMBS. A row-level lock, as the name suggests, locks down the access of a row to a transaction on "first come first serve" basis. This lock comes in multiple different flavours, each defining a level of access of the locked row. For example, a write lock can be acquired when you intend to insert/update a row so that no other transaction can access this row. Locks work hand in hand with isolation levels which define access at transaction level. Every RDMBS has its own implementation of locks but NHibernate makes it easy to work with any of these by defining a common semantics defined via an enum LockMode.

This enum defines the following lock modes:

  • Write: A transaction intending to insert a new row or update an existing row would automatically acquire this lock.
  • Upgrade: This lock mode can be acquired explicitly by the user when a record is selected with the intention of updating it. We are going to use this in the next example where we would discuss this in more detail.
  • UpgradeNoWait: Similar to upgrade lock with one difference – if the row is already locked by other user, then the database does not wait and returns immediately with a message that the lock could not be acquired.
  • Read: A read lock is acquired automatically when NHibernate reads any data.
  • None: Absence of a lock.

Pessimistic concurrency control is not set globally as part of mapping; it is instead set either on session or can be declared while loading an entity. Following code snippet would set the lock while the Employee entity is being loaded:

using (var tx = Database.Session.BeginTransaction())
{
    var emp = Database.Session.Get<Employee>(id, LockMode.Upgrade);
    emp.Firstname = "Hillary";
    emp.Lastname = "Gamble";
    tx.Commit();
}

You can notice in the following code that the SELECT statement generated by the preceding code has a row-level upgrade lock:

SELECT employee0_.Id            AS Id0_0_, 
       employee0_.Firstname     AS Firstname0_0_, 
       employee0_.Lastname      AS Lastname0_0_, 
       employee0_.EmailAddress  AS EmailAdd5_0_0_, 
       employee0_.DateOfBirth   AS DateOfBi6_0_0_, 
       employee0_.DateOfJoining AS DateOfJo7_0_0_, 
       employee0_.IsAdmin       AS IsAdmin0_0_, 
       employee0_.Password      AS Password0_0_ 
FROM   Employee employee0_ WITH (updlock, rowlock) 
WHERE  employee0_.Id=@p0;@p0 = 3465

The lock ensures that the row remains locked as long as the transaction in which the lock was issued is either committed or rolled back. This lock prevents other transactions from updating this record while the current transaction is still working on it. When the current transaction is done with the record, which is often indicated via commit of the transaction, then the lock is released. If the current transaction does not acquire a lock immediately, then it will wait.

Eventually, it will time out and throw an exception, in which case the session object would become unusable and we would have to start all over again.

If you want to acquire the row level lock in a separate statement, then the following is how you can do it:

using (var tx = Database.Session.BeginTransaction())
{
  var emp = Database.Session.Get<Employee>(id);
  Database.Session.Lock(emp, LockMode.Upgrade);
  emp.Firstname = "Hillary";
  emp.Lastname = "Gamble";
  tx.Commit();
}

In a nutshell, the preceding does the same thing, but instead of issuing a single SELECT statement, it issues two SELECT statements. One to load the entity and the other to lock the row explicitly.

You may have guessed that an explicit row level lock would keep other threads waiting, which is not ideal. Under heavy load, you may end up getting too many database transactions waiting to acquire a lock, eventually timing out, and giving a bad user experience.

..................Content has been hidden....................

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