I recently had to map an enum with 3 possible values (Pending, Approved, denied) to a nullable bit field in a SQL database. This type of mapping is not supported natively by NHibernate which by default translates enum values to their integer representation. I found several articles on handling the mapping to a string which is pretty straight forward using the … type but I haven’t found anything about this particular scenario.
This is where custom user types come to the rescue and this is what I am going to explain in this article.
To create a custom user type, we just need to create a class that implements NHibernate.UserTypes.IUserType. In our case, the implentation is pretty easy with the only methods that required a little bit of thinking being NullSafeGet and NullSafeSet.
This is how the class looks like:
public class NullableApprovalStatusType : IUserType
{
public bool Equals(object x, object y)
{
return x == null ? y == null : x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names,
object owner)
{
bool? dbValue = (bool?) NHibernateUtil.Boolean
.NullSafeGet(rs, names);
switch (dbValue)
{
case true:
return ApprovalStatusType.Approved;
case false:
return ApprovalStatusType.Denied;
default:
return ApprovalStatusType.Pending;
}
}
public void NullSafeSet(IDbCommand cmd, object value,
int index)
{
var obj = (ApprovalStatusType) value;
bool? dbValue = null;
switch (obj)
{
case ApprovalStatusType.Approved:
dbValue = true;
break;
case ApprovalStatusType.Denied:
dbValue = false;
break;
case ApprovalStatusType.Pending:
dbValue = null;
break;
}
NHibernateUtil.Boolean.NullSafeSet(cmd, dbValue,index);
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target,
object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.Boolean)}; }
}
public Type ReturnedType
{
get { return typeof(ApprovalStatusType); }
}
public bool IsMutable
{
get { return false; }
}
}
Now that we have our custom user type ready we can just do out mapping:
<property name="ApprovalStatus" column="is_approved"
not-null="false"
type="MyNamespace.NullableApprovalStatusType,
MyAssembly" />
It is nice to notice that the custom user type doesn’t have to be in the same namespace or assembly as the mapped class which allows us to keep it in the data access layer and not introduce any NHibernate dependency to the business logic or domain objects.