Mapping Enum Types to Integer Columns in NHibernate

Mapping enum types in NHibernate should be simple right? Well yes, it is, sort of….

Unfortunately the documentation is a little light in this area and many new comers fall into the same traps, sometimes without even realizing it. Often you will see examples of enum types being mapped to integer columns as follows:

this.Map(x => x.Enum)
  .CustomType(typeof(int));

The problem with this approach is that NHibernate will record an integer value in the original state when reading the object, but will compare it to an enum type value when determining if the property is dirty. As (1 != Enum.One) this leads to NHibernate always treating enum properties as dirty and in turn their defining objects. This is clearly not desirable as it leads to unnecessary database updates, additional audits, etc.

To work around this issue NHibernate provides the PersistentEnumType class. The PersistentEnumType simply converts values read from the database into their corresponding enum values so that NHibernate can compare them correctly.

We can define a descendent of PersistentEnumType for each enum type we want to map and change our mapping configuration as follows:

public class PersistentMyEnum : PersistentEnumType
{
  public PersistentMyEnum() : base(typeof(MyEnum))
  {
  }
}

this.Map(x => x.Enum)
  .CustomType(typeof(PersistentMyEnum);
      

A slightly less tedious approach would be to leverage generics.

public class PersistentEnumType<T> : PersistentEnumType
{
  public PersistentEnumType() : base(typeof(T))
  {
  }
}

this.Map(x => x.Enum)
  .CustomType(typeof(PersistentEnumType<TestMappingEnum>));

Unfortunately we cannot create a generic constraint to verify the type provided is an enum type, however, for those that worry about such things, we can provide a level of protection by adding an extension method to the PropertyPart class.

public static class PropertyPartExtensions
{
  public static PropertyPart EnumType<TEnum>(
    this PropertyPart part) where TEnum : struct 
  {
    if (!typeof(TEnum).IsEnum)
    {
      throw new ArgumentException("Generic argument TEnum must be an enum type.");
    }

    return part.CustomType(typeof(PersistentEnumType<TEnum>));
  }
}

this.Map(x => x.Enum)
  .EnumType<MyEnum>();

Another obvious benefit of using an extension method is IntelliSense discoverability.

There are other scenarios where you may want to customise the mapping performed by the PersistentEnumType; for example if we want to map (Enum.None = 0) to DBNull. To enable this and other mappings you will need to descend from the PersistentEnumType and override the NullSafeSet and NullSafeSet methods as shown below.

public class DefaultNullPersistentEnumType<T> : PersistentEnumType<T>
{
  public override void NullSafeSet(IDbCommand command, object value, 
    int index, bool[] settable, NHibernate.Engine.ISessionImplementor session)
  {
    base.NullSafeSet(command, (int)value != 0 ? value : null, 
      index, settable, session);
  }

  public override object NullSafeGet(IDataReader rs, string name)
  {
    var result = base.NullSafeGet(rs, name);
    if (result == null || result is DBNull)
    {
      result = 0;
    }

    // must cast to T to ensure dirty tracking is handled correctly
    return (T)result;
  }
}

Note that NullSafeGet should cast the object to the correct return type (T in our case) to ensure that dirty tracking is performed correctly.

To save yourself a lot of headaches tracking down hard to find issues you should ensure that you write sufficient tests for any custom types you develop to ensure values are read and written correct to the database, and that dirty tracking is performed properly.

About these ads
  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: