Implementing custom types in nHibernate

nHibernate can persist a lot of different datatypes, of course all the basic types in the .NET framework and some more sophisticated. However if you feel some type is missing go ahead and implement it. All that is required is that your type implements the IUserType interface shown below:

public interface IUserType
{
	/// <summary>
	/// The SQL types for the columns mapped by this type. 
	/// </summary>
	SqlType[] SqlTypes { get; }

	/// <summary>
	/// The type returned by <c>NullSafeGet()</c>
	/// </summary>
	System.Type ReturnedType { get; }

	/// <summary>
	/// Compare two instances of the class mapped by this type for persistent "equality"
	/// ie. equality of persistent state
	/// </summary>
	/// <param name="x" /></param>
	/// <param name="y" /></param>
	/// <returns></returns>
	bool Equals(object x, object y);

	/// <summary>
	/// Get a hashcode for the instance, consistent with persistence "equality"
	/// </summary>
	int GetHashCode(object x);

	/// <summary>
	/// Retrieve an instance of the mapped class from a JDBC resultset.
	/// Implementors should handle possibility of null values.
	/// </summary>
	/// <param name="rs" />a IDataReader</param>
	/// <param name="names" />column names</param>
	/// <param name="owner" />the containing entity</param>
	/// <returns></returns>
	/// <exception cref="HibernateException">HibernateException</exception>
	object NullSafeGet(IDataReader rs, string[] names, object owner);

	/// <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" />a IDbCommand</param>
	/// <param name="value" />the object to write</param>
	/// <param name="index" />command parameter index</param>
	/// <exception cref="HibernateException">HibernateException</exception>
	void NullSafeSet(IDbCommand cmd, object value, int index);

	/// <summary>
	/// Return a deep copy of the persistent state, stopping at entities and at collections.
	/// </summary>
	/// <param name="value" />generally a collection element or entity field</param>
	/// <returns>a copy</returns>
	object DeepCopy(object value);

	/// <summary>
	/// Are objects of this type mutable?
	/// </summary>
	bool IsMutable { get; }

	/// <summary>
	/// During merge, replace the existing (<paramref name="target" />) value in the entity
	/// we are merging to with a new (<paramref name="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 value from the detached entity being merged</param>
	/// <param name="target" />the value in the managed entity</param>
	/// <param name="owner" />the managed entity</param>
	/// <returns>the value to be merged</returns>
	object Replace(object original, object target, object owner);

	/// <summary>
	/// Reconstruct an object from the cacheable representation. At the very least this
	/// method should perform a deep copy if the type is mutable. (optional operation)
	/// </summary>
	/// <param name="cached" />the object to be cached</param>
	/// <param name="owner" />the owner of the cached object</param>
	/// <returns>a reconstructed object from the cachable representation</returns>
	object Assemble(object cached, object owner);

	/// <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. (optional operation)
	/// </summary>
	/// <param name="value" />the object to be cached</param>
	/// <returns>a cacheable representation of the object</returns>
	object Disassemble(object value);
}

Most of these is comments(which are rather useful and hinting the implementation), actually you need to implement only 8 methods and a couple of properties. The most straightforward thing to implement is immutable types, one good example is System.Uri which is immutable and can keep an Uri. When we store this in our database we can just save it as a string but in our application it would be nice to get features for getting the domain etc. and these are built into the System.Uri class so why not make nHibernate return instances of this instead of strings. So lets go ahead and implement the IUserType interface:

public class UriType : IUserType
{
    public SqlType[] SqlTypes
    {
        get
        {
            //We store our Uri in a single column in the database that can contain a string
            SqlType[] types = new SqlType[1];
            types[0] = new SqlType(DbType.String);
            return types;
        }
    }

    public System.Type ReturnedType
    {
        get { return typeof(Uri); }
    }

    public new bool Equals(object x, object y)
    {
        //Uri implements Equals it self by comparing the Uri's based 
        // on value so we use this implementation
        if (x == null)
        {
            return false;
        }else
        {
            return x.Equals(y);
        }
    }

    public int GetHashCode(object x)
    {
        //Again URL itself implements GetHashCode so we use that
        return x.GetHashCode();
    }

    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        //We get the string from the database using the NullSafeGet used to get strings 
        string uriString = (string) NHibernateUtil.String.NullSafeGet(rs, names[0]);
        
        //And save it in the Uri object. This would be the place to make sure that your string 
        //is valid for use with the System.Uri class, but i will leave that to you
        Uri result = new Uri(uriString);
        return result;
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        //Set the value using the NullSafeSet implementation for string from NHibernateUtil
        if (value == null)
        {
            NHibernateUtil.String.NullSafeSet(cmd, null, index);
            return;
        }
        value = value.ToString(); //ToString called on Uri instance
        NHibernateUtil.String.NullSafeSet(cmd, value, index);
    }

    public object DeepCopy(object value)
    {
        //We deep copy the uri by creating a new instance with the same contents
        if (value == null) return null;
        return new Uri(value.ToString());
    }

    public bool IsMutable
    {
        get { return false; }
    }

    public object Replace(object original, object target, object owner)
    {
        //As our object is immutable we can just return the original
        return original;
    }
    
    public object Assemble(object cached, object owner)
    {
        //Used for casching, as our object is immutable we can just return it as is
        return cached;
    }

    public object Disassemble(object value)
    {
        //Used for casching, as our object is immutable we can just return it as is
        return value;
    }
}

So that's it, pretty straight forward as we have an immutable type and using an already existing type as storage. This allows us to use this types NullSafeSet/NullSafeGet implementations.

To use this in our application and write code like this:

Website intellectWebsite = new Website();
intellectWebsite.Url = new Uri("http://www.intellect.dk/");
sess.Save(intellectWebsite);

We have to specify our type in the mapping file, its pretty easy we just have to tell nHibernate what type our property is, that is UriType in this case:

<class name="Website">
	<id name="ID">
		<generator class="native" />
	</id>
	<!-- this is the interresting line the type supplied is the name of our class with full namespace and the assembly it resides in -->
	<property name="Url" type="MyCompany.MyApplication.UriType, MyCompany.MyApplication"></property>
</class>

Thats it, now nHibernate knows how to persist an Uri and how to retrieve it again.

Posted on 18 Mar 2008 by Jakob T. Andersen
blog comments powered by Disqus