Click here to Skip to main content
Email Password   helpLost your password?

Contents

Introduction

According to the NHibernate documentation, version 1.2.0, NHibernate recommends that timestamp data type should be used for optimistic locking:

"Version numbers may be of type Int64, Int32, Int16, Ticks, Timestamp, or TimeSpan (or their nullable counterparts in .NET 2.0)".

But it seems there is a small issue when casting the type Byte[] column of SQL Server to the type TimestampType column of NHibernate on mapping file. Some questions related to this problem have already been posted on NHibernate forum like this one.

Another issue I have already faced is that the new application will be deployed on some states and the abbreviation of each state will be the prefix of tables. For example, if it is deployed at Indiana, Users table will become In_Users, and Tx_Users for Texas, Fl_Users for Florida respectively etc. Definitely I am not encouraged to open all XML mapping files, changing the table names when it is deployed on a different state.

In order to go through these issues, I just tried to implement interface of NHibernate classes based on the real requirements.

In this article, I would like to share my hands-on experience about these issues. Hopefully, you found it useful and can adapt it to meet your specific needs.

Set Up Demo

My demo is implemented with Visual Studio 2005, NHibernate version 1.2.0. If you are interested in this demo, it needs to run the NHibernateUsers.sql script included on the package to create NHibernateUsers database.

Screenshot - ImplementInterfaceNHibernate_1.jpg

Figure 1: NHibernateUsers database.

For the purposes of this article, let's assume that NHibernateUsers database is set up at Indiana so the prefix of tables must be IN_ and it is created by web_sa user. Rowversion column of IN_Users table is designed for checking optimistic locking. Here is a Users mapping file of IN_Users table (Users.hbm.xml):

<?xml version="1.0" encoding="utf-8">
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property"/>
    <class name="OverwriteInterfaceNHibernate.Users, OverwriteInterfaceNHibernate"
            table="Users" dynamic-update="true"
          optimistic-lock="version" mutable="true" polymorphism="implicit"
            dynamic-insert="false" select-before-update="false">
        <id name="UserID" column="userID"
            access="nosetter.camelcase" unsaved-value="0"/>
            <generator class="identity">
        </id>
        <version name="RowVersion" column="RowVersion"
            type="OverwriteInterfaceNHibernate.UserTypeTimestamp,
            OverwriteInterfaceNHibernate" generated="always" unsaved-value="null">
        <property name="UserName" column="UserName"
            type = "String(30)" not-null="true"/>
        <property name="Password" column="Password"
            type = "String(30)" not-null="true"/>
     </class>
</hibernate-mapping> 

Glancing at the mapping file, if you pay attention to the mapping file shown, there are two points. I think they should be following questions:

  1. Table is named the Users (table="Users") even if it is the IN_Users in database.
    This session is going to be discussed in the section Handle the prefix of tables and database owner in NHibernate query.
  2. RowVersion column is used for concurrency control, but the type of this column (UserTypeTimestamp) looks so strange.
    <version> is surely declared to ask NHibernate for completely handling optimistic locking.
    The issue happens when RowVersion column is declared the timestamp data type.
    [NHibernate.ADOException] = {"Could not cast the value in field RowVersion1_ of type Byte[] to the Type TimestampType.
    Please check to make sure that the mapping is correct and that your DataProvider supports this Data Type."}

There is definitely a difference between data type of NHibernate and SQL Server. So it is surely that I cannot use timestamp data type for RowVersion column.

Luckily, Nhibernate gives me the ability to define a new data type. So in this case, my approach is: try to define the new data type that is consistent with type Byte[].

Define New User Type to Check Optimistic Locking with Timestamp Data Type

NHibernate allows us to easily define any kind of custom data type called user types. User types can be used for various different purposes. In my case, it is used to convert a datatype, exactly type Byte[]. Instead of using the timestamp data type, the RowVersion column is UserTypeTimestamp. UserTypeTimestamp class is merely implementing Nibernate.IUserType:

using System;
using System.Collections.Generic;
using System.Text;
using NHibernate.Cfg;
using NHibernate;
using NHibernate.Type;
using NHibernate.SqlTypes;

namespace OverwriteInterfaceNHibernate
{
    /// <summary>
    /// UserTypeTimestamp implements the Nhibernate BinaryType
    /// that is used to handle Nhibernate version.
    /// </summary>
    class UserTypeTimestamp : NHibernate.UserTypes.IUserVersionType
    {
        #region IUserVersionType Members

        public object Next(object current, NHibernate.Engine.ISessionImplementor session)
        {
            try
            {
                return current;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public object Seed(NHibernate.Engine.ISessionImplementor session)
        {
            try
            {
                return new byte[8];
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        #endregion

        #region IUserType Members

        public object Assemble(object cached, object owner)
        {
            try
            {
                return DeepCopy(cached);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        public object DeepCopy(object value)
        {
            try
            {
                return value;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        public object Disassemble(object value)
        {
            try
            {
                return DeepCopy(value);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        public int GetHashCode(object x)
        {
            try
            {
                return ((byte[])x).GetHashCode();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        public bool IsMutable
        {
            get { return false; }
        }

        public object NullSafeGet(System.Data.IDataReader rs, string[] names,
            object owner)
        {
            try
            {
                return ((byte[])rs.GetValue(rs.GetOrdinal(names[0])));
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
        {
            try
            {
                NHibernateUtil.Binary.NullSafeSet(cmd, value, index);
                return;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public object Replace(object original, object target, object owner)
        {
            return original;
        }

        public Type ReturnedType
        {
            get { return typeof(byte[]); }
        }

        public SqlType[] SqlTypes
        {
           get
            {
                try
                {
                  qlType[] types = { new NHibernate.SqlTypes.SqlType
                (System.Data.DbType.Binary) }; return types;
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
        }

        #endregion

        #region IComparer Members

        public int Compare(object x, object y)
        {
            try
            {
                byte[] xbytes = (byte[])x;
                byte[] ybytes = (byte[])y;
                if (xbytes.Length < ybytes.Length)
                {
                    return -1;
                }
                if (xbytes.Length > ybytes.Length)
                {
                    return 1;
                }
                for (int i = 0; i < xbytes.Length; i++)
                {
                    if (xbytes[i] < ybytes[i])
                    {
                        return -1;
                    }
                    if (xbytes[i] > ybytes[i])
                    {
                        return 1;
                    }
                }
                return 0;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
       bool NHibernate.UserTypes.IUserType.Equals(object x, object y)
        {
            return (x == y);
        }
        #endregion
    }
}

It seems the implementation is rather straightforward. My conversion class is ready now; everything I need to do is just change the version property mapping definition to use an instance of the conversion class instead of timestamp:

 <version name="RowVersion" column="RowVersion"
    type="OverwriteInterfaceNHibernate.UserTypeTimestamp,
OverwriteInterfaceNHibernate" generated="always" unsaved-value="null">

The next step is to create a class for domain object. I just needed to duplicate table structure in the class and provide a way for linking the Users table. rowversion property was declared with the byte[] data type.

using System;
using System.Collections.Generic;
using System.Text;
namespace ImplementInterfaceNHibernate
{
    [Serializable]
    public class Users
    {
        private int userID;
        private string username;
        private string password;
        private byte[] rowversion;
        public virtual int UserID
        {
            get { return this.userID; }
            set { this.userID = value; }
        }
        public virtual string UserName
        {
            get { return this.username; }
            set { this.username = value; }
        }
        public virtual string Password
        {
            get { return this.password; }
            set { this.password = value; }
        }
        public virtual byte[] RowVersion
        {
            get { return this.rowversion; }
            set { this.rowversion = value; }
        }
    }
}

Screenshot - ImplementInterfaceNHibernate_2.jpg

Figure 2: Automatically checking optimistic locking:

That's all. In this test, I tried to concurrently update password of "Admin" user. So what's happening ... when "admin 1" password was updated, Nhibernate automatically checked RowVersion value, there was no change, so the process was successful but "admin 2" because RowVersion was already changed by SQL Server when "admin 1" was executed.

Handle the Prefix of Tables and Database Owner in NHibernate Query

As mentioned in the introduction, the prefix of table is also an interesting session to dig. In the demo,IN_ is currently the prefix of Users table. It should flexibly be changed to AL_ or FL_ etc. In some cases, database is created by other users, not dbo, it is a root cause to make an error. Looking at the User.hbm.xml file, I just defined table="Users". It means the mapping file is never modified in spite of the change of prefix. How can NHibernate correctly generate an SQL statement for this case? The easy way I did is to ask NHibernate for adding the prefix when generating SQL statement. In order to do that, I need to implement the INamingStrategy interface. PrefixNamingStrategy class is simply created for this purpose.

using System;
using System.Collections.Generic;
using System.Text;
using NHibernate.Cfg;
namespace ImplementInterfaceNHibernate
{
    /// <summary>
    /// PrefixNamingStrategy class will generate prefixes for tables
    /// in the SQL statements which
    /// are generated by Hibernate
    /// </summary>
    class PrefixNamingStrategy : INamingStrategy
    {
        String tableNamePrefix;
        #region INamingStrategy Members
        public string ClassToTableName(string className)
        {
            try
            {
                return TableName(className);
            }
            catch (Exception ex) { throw ex; }

        }
        public string ColumnName(string columnName)
        {
            try
            {
                return columnName;
            }
            catch (Exception ex) { throw ex; }
        }
        public string PropertyToColumnName(string propertyName)
        {
            try
            {
                return ColumnName(propertyName);
            }
            catch (Exception ex) { throw ex; }
        }
        public string PropertyToTableName(string className, string propertyName)
        {
            try
            {

                return PropertyToColumnName(propertyName);
            }
            catch (Exception ex) { throw ex; }
        }
        public string TableName(string tableName)
        {
            try
            {
                return tableNamePrefix + tableName;
            }
            catch (Exception ex) { throw ex; }
        }
        public void setTableNamePrefix(String s)
        {
            try
            {
                tableNamePrefix = s;
            }
            catch (Exception ex) { throw ex; }
        }
        #endregion
    }
}

In PrefixNamingStrategy class, there is only one more function added to include the owner and the prefix of tables of database: setTableNamePrefix. Before building SessionFactory of NHibernate, the owner and the prefix of tables database should be added to configuration instance:

public UserDataAccess()
  {
            PrefixNamingStrategy namer = new PrefixNamingStrategy();
            namer.setTableNamePrefix("web_sa.IN_");
            nhConfig = new Configuration();

            if (nhConfig == null)
            {
                throw new InvalidOperationException
                      ("NHibernate configuration is null.");
            }
            nhConfig.SetNamingStrategy(namer);
            nhConfig.AddClass(typeof(ImplementInterfaceNHibernate.Users));
            nhFactory = nhConfig.BuildSessionFactory();
            if (nhFactory == null)
            {
                throw new InvalidOperationException
                    ("Call to Configuration.BuildSessionFactory() returned null.");
            }
  }

This is everything I did. Now NHibernate correctly generates SQL statements I expect. It is just my suggestion, owner and the prefix of tables can be put in the configuration file or anywhere you feel comfortable.

Points of Interest

In these particular cases, I resolved to pass the issues by implementing NHibernate interface. Adding the implementation NHibernate interface skill to your development techniques will surely ease your experience when developing with NHibernate.

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralMy vote of 1
binarycheese
9:08 14 Apr '09  
Stop misleading junior developers with this horribly written code
GeneralException handling is terrible
gerektoolhy
8:09 14 Apr '09  
As already other commented have pointed out, the exception handling is terrible.
GeneralMy vote of 1
MeNot
6:43 14 Apr '09  
some of the worst practices imaginable. the entire article should be removed to prevent reuse.
GeneralOptimistic locking for Oracle
Jame Miller_NC
5:58 14 Apr '09  
Thanks your solution. Could it apply to Oracle?
Regards,
GeneralMy vote of 1
mark_shiffer
3:56 14 Apr '09  
Poorly written code that clearly violates many principles of software development.
GeneralMy vote of 1
jeff.barnes
21:38 13 Apr '09  
Promotes poor code quality and design
GeneralRe: My vote of 1 [modified]
Dungtran
21:46 13 Apr '09  
Dear Sir.
When I worked on NHibernate, I saw some question related optimistic locking questions on Hibernate forum like "Topic: version related problem using timestamp ===&gt; Urgent", you can reach at: http://forum.hibernate.org/viewtopic.php?t=962174

So I just wanted to suggest the solution to solve optimistic locking with version/ timestamp columns in NHibernate. It was just a DEMO. I didn't intent to show HOW TO USE "TRY ... CATCH" in C#.

Thanks

modified on Tuesday, April 14, 2009 5:02 AM

GeneralMy vote of 1
paragme
20:36 13 Apr '09  
I think you should first get the faundamentals right, then proceed to writing an article.
GeneralRe: My vote of 1
Dungtran
0:06 14 Apr '09  
Dear Sir.
When I worked on NHibernate, I saw some question related optimistic locking questions on Hibernate forum like "Topic: version related problem using timestamp ===&amp;gt; Urgent", you can reach at: http://forum.hibernate.org/viewtopic.php?t=962174

So I just wanted to suggest the solutionto solve optimistic locking with version/ timestamp columns in NHibernate. It was just a DEMO. I didn't intent to show HOW TO USE "TRY ... CATCH" in C#.

Thanks
GeneralYou made Ayende cry
Judah Himango
17:42 13 Apr '09  
Congrats, you made Oren Eini cry[^]. Smile

Religiously blogging on the intarwebs since the early 21st century: Kineti L'Tziyon Judah Himango


GeneralRe: You made Ayende cry [modified]
Dungtran
21:48 13 Apr '09  
Dear Sir.
When I work on NHibernate, I saw some question related optimistic locking questions on Hibernate forum like "Topic: version related problem using timestamp ===&gt; Urgent", you can reach at: http://forum.hibernate.org/viewtopic.php?t=962174

So I just wanted to suggest the solution to solve optimistic locking with version/ timestamp columns in NHibernate. It was just a DEMO. I didn't intent to show HOW TO USE "TRY ... CATCH" in C#.

Thanks

modified on Tuesday, April 14, 2009 5:04 AM

GeneralRe: You made Ayende cry
Judah Himango
4:50 14 Apr '09  
I understand, and perhaps we are being too harsh on you.

I suggest understanding try/catch better before writing more articles.

Religiously blogging on the intarwebs since the early 21st century: Kineti L'Tziyon Judah Himango


GeneralRe: You made Ayende cry
Alexey Romanov
2:28 15 Apr '09  
Did you intend to show how to never, ever use "try ... catch" in C#? Because you did, whether you intended it or not.
GeneralMy vote of 1
Anton Afanasyev
14:47 13 Apr '09  
Ladies and gentlemen...I present to you...._The_ WTF!
GeneralRe: My vote of 1
Dungtran
0:05 14 Apr '09  
Dear Sir.
When I worked on NHibernate, I saw some question related optimistic locking questions on Hibernate forum like "Topic: version related problem using timestamp ===&amp;gt; Urgent", you can reach at: http://forum.hibernate.org/viewtopic.php?t=962174

So I just wanted to suggest the SOLUTION to solve optimistic locking with version/ timestamp columns in NHibernate. It was just a DEMO. I didn't intent to show HOW TO USE "TRY ... CATCH" in C#.

Thanks
GeneralMy vote of 1
Mufasa245
14:01 13 Apr '09  
Bad code samples for one. DeepCopy that just returns the original object? Fail.
GeneralRe: My vote of 1 [modified]
Dungtran
21:54 13 Apr '09  
Dear sir,

I don't think it is bad code. DeepCope was implemented the interface of "IUserVersionType.cs" class of NHibernate. In this case, I did nothing, just returned the old value of NHibernate returned.

Thanks.

modified on Tuesday, April 14, 2009 5:03 AM

GeneralMy vote of 1
Puchko Vasili
13:17 13 Apr '09  
I want to cry..
public object DeepCopy(object value)
{
try
{
return value;
}
catch (Exception ex)
{
throw ex;
}
}
===
try
{
..
}
catch (Exception ex) { throw ex; }

If you want to make the world more better do not write any more code, pleaseFrown ...
GeneralRe: My vote of 1 [modified]
Dungtran
21:44 13 Apr '09  
Dear Sir.
When I worked on NHibernate, I saw some question related optimistic locking questions on Hibernate forum like "Topic: version related problem using timestamp ===> Urgent", you can reach at: http://forum.hibernate.org/viewtopic.php?t=962174

So I just wanted to suggest the solution to solve optimistic locking with version/ timestamp columns in NHibernate. It was just a DEMO. I didn't intent to show HOW TO USE "TRY ... CATCH" in C#.

Thanks

modified on Tuesday, April 14, 2009 5:03 AM

GeneralException Handling
danderson00
3:30 9 Jun '08  
Please now shoot yourself for wrapping each and every member in a try..catch block that does worse than nothing... It swallows the stack trace. You could replace each throw ex; line with simply throw; which would preserve the stack trace but is then completely superfluous.

Sorry can't stand seeing stuff like that.
GeneralRe: Exception Handling [modified]
Dungtran
21:56 13 Apr '09  
Dear Sir.
When I worked on NHibernate, I saw some question related optimistic locking questions on Hibernate forum like "Topic: version related problem using timestamp ===&gt; Urgent", you can reach at: http://forum.hibernate.org/viewtopic.php?t=962174

So I just wanted to suggest the solution to solve optimistic locking with version/ timestamp columns in NHibernate. It was just a DEMO. I didn't intent to show HOW TO USE "TRY ... CATCH" in C#.

Thanks

modified on Tuesday, April 14, 2009 5:02 AM

GeneralRe: Exception Handling
Pop Catalin
0:42 14 Apr '09  
Dear Sir,

I as a member of CodeProject and a .Net developer, I appreciate your effort and time you took to write the article.

But the .Net community like all programming communities is in an ongoing battle against bad coding practices, there are hundreds of books and tens of thousands of blog posts out there speaking about good coding practices in .Net, the community is engaged in an ongoing effort to create better code and raise the quality bar of the code the .Net developers write.

What you have done in this article is one of the worst practices regarding exception handling, and that can't be hidden behind an excuse like, it's only "demo" code. I's ok for demo code not to have sophisticated error handling, but is not ok to have error handling code that represents the worst practices in .Net. The reasons are obvious, people will read and take the code as an example.

If you want the people to learn from your code, make it so that they don't accidentally learn bad practices from it.

Sincerely,
Pop Catalin

"I haven't lost my mind, it's backed up on tape somewhere."

GeneralOptimistic locking great - but there's one limitation
dragonwell
7:17 18 Oct '07  
Thanks for posting this - it is very helpful. The UserTypeTimestamp works great with the TimeStamp colum in sql server. However, there is one case where is is not compatible with how NHibernate handles a version field. It is when there is a Bag property in the class with cascading operations enabled. During a Delete operation, there is an "extra" UPDATE query performed immediately before actually deleting which causes the timestamp value to be changed in the table but not in the object. This causes the actual DELETE to fail.

I don't know why NHibernate does this one last UPDATE before the delete, but it is only done when the class contains a bag with cascade="all|save-update|delete|all-delete-orphan".

I think it is done because normally a version field with NHibernate Timestamp (.NET DateTime) is controlled by NHibernate and not by the database, and thus NHibernate can update it as needed eve if it's the same vaule. Hope that makes sense.. Unsure )

Maybe you know of a workaround?
GeneralRe: Optimistic locking great - but there's one limitation
Llejdi Lloth
4:23 30 Sep '08  
Just mark the collection that causes the problem (the cascaded one) with optimistic-lock="false" in NHibernate mapping.


Last Updated 16 Sep 2007 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010