NHibernate NullableDateTime custom type

NHibernate doesn’t support mappings for nullable DateTime types by default. Fortunately, this is easy to implement using the IUserType interface. To demonstrate this, create a new Console Application project and add FluentNHibernate using NuGet. Then add the following class:

using System;
using System.Data;
using NHibernate;
using NHibernate.SqlTypes;
using NHibernate.UserTypes;

namespace UserTypes
{
    /// <summary>
    /// Type to allow NHibernate to persist DateTime? objects
    /// </summary>
    public class NullableDateTimeType : IUserType
    {
        /// <summary>
        /// Gets a value indicating whether the value is mutable
        /// </summary>
        public bool IsMutable
        {
            get
            {
                // This item is immutable:
                return false;
            }
        }

        /// <summary>
        /// Gets the type returned by NullSafeGet()
        /// </summary>
        public Type ReturnedType
        {
            get
            {
                return typeof(DateTime?);
            }
        }

        /// <summary>
        /// Gets the SQL types for the columns mapped by this type. 
        /// </summary>
        public SqlType[] SqlTypes
        {
            get
            {
                return new[]
                {
                    new SqlType(DbType.DateTime)
                };
            }
        }

        /// <summary>
        /// Reconstruct an object from the cacheable representation. At the very least this method should perform a deep copy if the type is mutable.
        /// </summary>
        /// <param name="cached">The cached object</param>
        /// <param name="owner">The owner object</param>
        /// <returns>The assemled object</returns>
        public object Assemble(object cached, object owner)
        {
            // Used for caching. As our object is immutable we can return as is:
            return cached;
        }

        /// <summary>
        /// Return a deep copy of the persistent state, stopping at entities and at collections. 
        /// </summary>
        /// <param name="value">The item to copy</param>
        /// <returns>The copied item</returns>
        public object DeepCopy(object value)
        {
            // We deep copy the item by creating a new instance with the same contents.
            // Note that this happens for free with value types because of the way 
            // that method parameters work:
            if (value == null)
            {
                return null;
            }

            return value as DateTime?;
        }

        /// <summary>
        /// Transform the object into its cacheable representation. At the very least this method should perform a deep copy 
        /// if the type is mutable. That may not be enough for some implementations, however; for example, associations must 
        /// be cached as identifier values.
        /// </summary>
        /// <param name="value">The cached object</param>
        /// <returns>The dassassemled object</returns>
        public object Disassemble(object value)
        {
            // Used for caching. As our object is immutable we can return as is:
            return value;
        }

        /// <summary>
        /// Compare two instances of the class mapped by this type for persistent "equality" ie. equality of persistent state 
        /// </summary>
        /// <param name="x">The first item</param>
        /// <param name="y">The second item</param>
        /// <returns>A value indicating whether the items are equal</returns>
        public new bool Equals(object x, object y)
        {
            if (x == null && y == null)
            {
                return true;
            }

            if (x == null)
            {
                return false;
            }

            return x.Equals(y);
        }

        /// <summary>
        /// Get a hashcode for the instance, consistent with persistence "equality" 
        /// </summary>
        /// <param name="x">The value to get the hash code for</param>
        /// <returns>The hash code</returns>
        public int GetHashCode(object x)
        {
            if (x == null)
            {
                return 0;
            }

            return x.GetHashCode();
        }

        /// <summary>
        /// Retrieve an instance of the mapped class from a resultset. Implementors should handle possibility of null values. 
        /// </summary>
        /// <param name="rs">The reader</param>
        /// <param name="names">The item names</param>
        /// <param name="owner">The owner object</param>
        /// <returns>The object requested</returns>
        public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            // We get the DateTime from the database using the NullSafeGet used to get strings from NHibernateUtil:
            return NHibernateUtil.DateTime.NullSafeGet(rs, names[0]) as DateTime?;
        }

        /// <summary>
        /// Write an instance of the mapped class to a prepared statement. Implementors should handle possibility of null values. A multi-column type should be written to parameters starting from index. 
        /// </summary>
        /// <param name="cmd">The command</param>
        /// <param name="value">The value to use</param>
        /// <param name="index">The index to set</param>
        public void NullSafeSet(IDbCommand cmd, object value, int index)
        {
            // Convert to the correct type and set:
            var dateTimeValue = value as DateTime?;

            if (dateTimeValue == null)
            {
                NHibernateUtil.DateTime.NullSafeSet(cmd, null, index);
            }
            else
            {
                NHibernateUtil.DateTime.NullSafeSet(cmd, dateTimeValue.Value, index);
            }
        }

        /// <summary>
        /// During merge, replace the existing (target) value in the entity we are merging to with a new (original) 
        /// value from the detached entity we are merging. For immutable objects, or null values, it is safe to 
        /// simply return the first parameter. For mutable objects, it is safe to return a copy of the first parameter. 
        /// For objects with component values, it might make sense to recursively replace component values. 
        /// </summary>
        /// <param name="original">The original value</param>
        /// <param name="target">The target value</param>
        /// <param name="owner">The owner object</param>
        /// <returns>The replacement object</returns>
        public object Replace(object original, object target, object owner)
        {
            // As our object is immutable we can just return the original  
            return original;
        }
    }
}

This class is responsible for telling NHibernate how to save and retrieve nullable DateTime values. The following demonstrates how this class can be used.

Given a class with a nullable DateTime property:

using System;

namespace UserTypes
{
    public class Student
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime? GraduationDate { get; set; }
    }
}

…the mapping would be as follows:

using FluentNHibernate.Mapping;
using UserTypes;

public class StudentMapping : ClassMap<Student>
{
    public StudentMapping()
    {
        Not.LazyLoad();
        Id(x => x.Id).GeneratedBy.Identity();
        Map(x => x.FirstName).Length(20).Not.Nullable();
        Map(x => x.LastName).Length(20).Not.Nullable();
        Map(x => x.GraduationDate).Nullable().CustomType(x => typeof(NullableDateTimeType));
    }
}

This can be tested by adding the following code to the Program class:

using System;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;

namespace UserTypes
{
    public static class Program
    {
        public static void Main()
        {
            var connectionString = "Data Source=(local);Initial Catalog=StudentTest;Integrated Security=True;Pooling=False";

            var sessionFactory = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString).ShowSql())
                .ExposeConfiguration(BuildSchema)
                .Mappings(x => x.FluentMappings.AddFromAssemblyOf<Student>())
                .BuildSessionFactory();

            // Create the session:
            using (var session = sessionFactory.OpenSession())
            {
                session.SaveOrUpdate(new Student { FirstName = "Not", LastName = "Graduated" });
                session.SaveOrUpdate(new Student { FirstName = "Already", LastName = "Graduated", GraduationDate = new DateTime(2000, 1, 1) });
            }

            using (var session = sessionFactory.OpenSession())
            {
                var students = session.QueryOver<Student>().List();
                foreach (var student in students)
                {
                    Console.WriteLine("{0} {1} {2}", student.FirstName, student.LastName, student.GraduationDate);
                }
            }

            Console.ReadLine();
        }

        private static void BuildSchema(Configuration configuration)
        {
            new SchemaExport(configuration).Create(false, true);
        }
    }
}

Note that the code above assumes there is a local instance of SQL Server running, with a database called StudentTest accessible using integrated authentication.

Running the code creates the following table, which includes a DateTime field that accepts null values as required:

NHibernateNullableDateTime01

…inserts the following data:

NHibernateNullableDateTime02

…and outputs the following:

NHibernateNullableDateTime03

This proves that the mapping can be used to store and retrieve null and non-null DateTime values as required.