Page 1
When creating .NET applications on top of a legacy database, you may encounter some strange data that require advanced mapping techniques if you want to use it in a well known .NET way. This is especially true when the database is Oracle and over 10 years old.
Today I encountered one of these mapping problems that got me stucked a little. I’m still very new to NHibernate, but this problem required me to search much deeper than the “hello world” application, but I found a solution which I want to share here.
Scenario
I ran into the following scenario. The original database table contains a column “status” which contains a letter, indicating the status of the object.
‘a’ = active, ‘d’ = deactive, ‘s’ = suspended and ‘n’ = no status.
When mapping this table to a .NET class, it is very common to use an enumerator to indicate the status of the object.
public enum ObjectStatus
{
Active,
Deactive,
Suspended,
None
}
Now the question is: How do we map the database field ‘a’ to ObjectStatus.Active ?
The answer is to be found in NHibernate Custom Mapping Types.
NHibernate.UserTypes.IUserType
NHibernate uses their own types to map .NET types with the proper DB Types. For example, the NHibernate.String type will map the System.String type with the DbType.String type. NHibernate provides you with the possibility to create your own mapping type by implementing the IUserType interface.
Below you’ll find the code of the entire class. I used mainly the example class explained in this article, mixed with the theory I read from the NHibernate in Action book.
using System.Data;
using NHibernate.UserTypes;
public class MyCustomStateType : IUserType
{
#region IUserType Members
public object Assemble(object cached, object owner)
{
return cached;
}
public object DeepCopy(object value)
{
return value;
}
public object Disassemble(object value)
{
return value;
}
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x == null ? typeof(ObjectStatus).GetHashCode() + 473 : x.GetHashCode();
}
public bool IsMutable
{
get { return false; }
}
public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
{
object obj = NHibernate.NHibernateUtil.String.NullSafeGet(rs, names[0]);
if (obj == null) return ObjectStatus.None;
string s = (String)obj;
switch (s)
{
case "a": return ObjectStatus.Active;
case "d": return ObjectStatus.Deactive;
case "s": return ObjectStatus.Suspended;
default: return ObjectStatus.None;
}
}
public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
{
if (value == null)
{
((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
}
else
{
EmployeeStatus status = (ObjectStatus)value;
string s = "n";
switch (status)
{
case ObjectStatus.Active: s = "a"; break;
case ObjectStatus.Deactive: s = "d"; break;
case ObjectStatus.Suspended: s = "s"; break;
case ObjectStatus.None: s = "n"; break;
}
((IDataParameter)cmd.Parameters[index]).Value = s;
}
}
public object Replace(object original, object target, object owner)
{
return original;
}
public Type ReturnedType
{
get { return typeof(ObjectStatus); }
}
public NHibernate.SqlTypes.SqlType[] SqlTypes
{
get { return new[]{NHibernate.NHibernateUtil.String.SqlType}; }
}
#endregion
}
Most of the interface methods are straight forward.
The 2 most important methods are NullSafeGet and NullSafeSet.
In NullSafeGet, you retrieve the data from the database, which comes in this case as a string value. We then inspect the value and return the corresponding enumerator value.
In NullSafeSet, we do exact the opposite. We inspect the enumerator value and pass the string value to be saved in the DB.
Using the custom type is very simple. Just set the type property in your mapping file for the corresponding class Property.
<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
auto-import="true">
<class name="MyLibrary.MyObject, MyLibrary" lazy="false">
<id name="id" access="field">
<generator class="native" />
</id>
<property name="name" access="field" column="name" />
<property name="Status" access="property" column="state" type="MyLibrary.MyCustomStateType, MyLibrary" />
</class>
</hibernate-mapping>
Having learned this feature, it becomes very handy. Especially when some DBA 10 years ago decided to use an ‘X’ and NULL value to indicate True or False :D