Logo

NHibernate

The object-relational mapper for .NET

Mapping different types - IUserType

Recently I had a problem with the application I’ve been working on. One of entity types in my domain had a property of type uint. Not a big deal, until you want to store it in Microsoft SQL Server database which does not support unsigned types. I’ve been scratching my head for a moment and then I found a solution – let’s map it as long in our database. Since long can represent any legal value of uint, we should be all good, right? So let’s do it.

public class ClassWithUintProperty
{
    private Guid Id { get; set; }
    public virtual uint UintProp { get ;set; }
}

 

 

 

 

 

<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="NHibernate.Test"
                   namespace="NHibernate.Test">
  <class name="ClassWithUintProperty">
    <id name="Id"/>
    <property name="UIntProp" not-null="true" type="long" />
  </class>
</hibernate-mapping>

 

 

"Houston, we've had a problem"

All good. At least until you try to fetch saved value from the database. When you do, you’re up for an unpleasant surprise:

System.InvalidCastException: Specified cast is not valid.

NHibernate.PropertyAccessException: Invalid Cast (check your mapping for property type mismatches); setter of NHibernate.Test.UIntAsLong

The exception is a result of how NHibernate optimizes property access and quirks of mixing different conversion operators (unboxing and numeric conversion in this case) and are not really interesting. What’s important, is that we approached the problem from the wrong angle.

What we’re dealing with here, is inability of our database engine to deal with datatype in our model, which we were trying to solve by pushing this onto NHibernate without telling it something is ‘”wrong”. While NHibernate is smart, it’s working based on a set of explicit information, so what we need to do, is to be explicit about what we want it to do.

There are two places where we can tell NHibernate about it.

  • IUserType, which will explicitly handle the mapping from uint in our model to long in the DB
  • custom Dialect which will basically lie to NHibernate telling it “yeah, sure this DB supports uints – whole dozens of ‘em!” and do some work under the covers to live up to its promise. (not shown in this post).

 

Enter IUserType

IUserType is an extension point in NHibernate that let’s you plug in to the mapping process and handle it yourself. The interface is quite big, but there’s very little real logic there:

public class UIntAsLong:IUserType
{
        public SqlType[] SqlTypes
        {
            get { return new[] { SqlTypeFactory.Int64 }; }
        }
 
        public Type ReturnedType
        {
            get { return typeof( uint ); }
        }
 
        public bool IsMutable
        {
            get { return false; }
        }
 
        public int GetHashCode( object x )
        {
            if( x == null )
            {
                return 0;
            }
            return x.GetHashCode();
        }
 
        public object NullSafeGet( IDataReader rs, string[] names, object owner )
        {
            object obj = NHibernateUtil.UInt32.NullSafeGet( rs, names0 );
            if( obj == null )
            {
                return null;
            }
            return (uint)obj;
        }
 
        public void NullSafeSet( IDbCommand cmd, object value, int index )
        {
            Debug.Assert( cmd != null);
            if( value == null )
            {
                ((IDataParameter)cmd.Parametersindex).Value = DBNull.Value;
            }
            else
            {
                var uintValue = (uint)value;
                ( (IDataParameter) cmd.Parametersindex ).Value = (long) uintValue;
            }
        }
 
        public object DeepCopy( object value )
        {
            // we can ignore it...
            return value;
        }
 
        public object Replace( object original, object target, object owner )
        {
            // we can ignore it...
            return original;
        }
 
        public object Assemble( object cached, object owner )
        {
            // we can ignore it...
            return cached;
        }
 
        public object Disassemble( object value )
        {
            // we can ignore it...
            return value;
        }
 
        bool IUserType.Equals( object x, object y )
        {
            return object.Equals( x, y );
        }
} 

There are really two parts of the code that are interesting. SqlTypes / ReturnedType properties which tell NHibernate which types to expect on both sides of the mapping, and the NullSafeGet / NullSafeSet methods which perform the actual conversion.

Now we just need to plug our custom user type to the mapping, and it goes like this:

<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="NHibernate.Test"
                   namespace="NHibernate.Test">
  <class name="ClassWithUintProperty">
    <id name="Id"/>
    <property name="UIntProp" not-null="true" type="Foo.Namespace.UIntAsLong, Foo.Assembly" />
  </class>
</hibernate-mapping>

 


Posted Thu, 15 October 2009 08:21:00 AM by krzysztof.kozmic
Filed under: mapping, IUserType

comments powered by Disqus
© NHibernate Community 2024