NHibernate Custom Mapping Types
(originally posted to Absolute Opinion on June 14, 2006)
As you should know by now, NHibernate is an object-relational mapping (ORM) tool that uses XML metadata files to marshal data between an object-oriented domain model and a relational database. Now, in the majority of use cases, the default support provided by NHibernate will be sufficient for saving and fetching your domain objects. However, there are cases where additional capabilities are required. Fortunately, NHibernate provides lots of extension points. I’m going to discuss 2 scenarios in this article – both of which take advantage of creating custom NHibernate mapping types.
Scenario 1:
You have an implementation of the Type Object Pattern. Simply put, you have a class to represent the type of an object, and then a class the references your type object. The static model looks like the following.
Now, let’s take this a step further and say that your type class takes a pretty large amount of time to construct and initialize. Therefore, you decide to implement a flyweight factory in order to minimize the performance impact on your application in terms of the number of type class instances that must be constructed. Fantastic! Now, let’s look at the ramifications on persistence.
From the object persistence perspective, the problem you are now facing is that you do not want to persist the object state for your TypeClass instance (since you are trying to control the number of instances for that class). However, you do want to persist enough information about the TypeClass instance so that when you load an instance of ActualClass from the database, you can get the correct TypeClass instance from the flyweight factory and apply it to your reconstructed ActualClass instance.
Now in a world without NHibernate (oh what a sad world!), we would create a data access class for ActualClass and add methods for setting and fetching ActualClass instances. Since the TypeClass reference exists outside of the database scope, we would add the association in the data access class. For example, your data access class may have code that looks like the following.
1: public static class ActualClassRepository { 2: public static void Save(ActualClass actualClass) { 3: IDbCommand cmd = null;
4: IDbDataParameter param;
5:
6: //build the sql command and parameters...
7: param = cmd.CreateParameter();
8: param.ParameterName = "@typeClass";
9: param.DbType = DbType.String;
10: param.Value = actualClass.Type.Name;
11: }
12:
13: public static ActualClass Get(int id) { 14: IDataReader rdr = null;
15:
16: //build the sql command to and get a data reader...
17: if (rdr.Read()) { 18: ActualClass ret = new ActualClass();
19: ret.Data = rdr.GetString(0);
20: TypeClassFactory factory = new TypeClassFactory();
21: ret.Type = factory.GetTypeClass(rdr.GetString(1));
22: }
23:
24: return null;
25: }
26: }
If you pay attention to the code shown in bold for both the get and set methods, you can see how your data access class might handle the setting and fetching of an object and its associated type object. When saving the ActualClass object, the data access object saves the name of the associated TypeClass object to the database. When the ActualClass object is restored from the database, the name of the TypeClass is retrieved and passed to our flyweight factory where the object instance can be acquired and set on the ActualClass object being built.
Now, this works fine in very simple cases like the one mentioned above. However, most object models have many more complex sets of associations. It is in cases like this where NHibernate reveals a great deal of strength – and it would be advantageous to be able to use NHibernate to solve our type object problem.
Enter the custom mapping type.
In NHibernate, a custom mapping type is a class that derives from either the IUserType or ICompositeUserType interfaces. These interfaces contain several methods that must be implemented, but for our purposes here, we’re going to focus on 2 of them. Consider the following.
1: public class TypeClassUserType : IUserType
2: { 3:
4: ...
5:
6: object IUserType.NullSafeGet(IDataReader rs,
7: string[] names,
8: object owner) { 9:
10: string name = NHibernateUtil.String.NullSafeGet(rs,
11: names[0]) as string;
12:
13: TypeClassFactory factory = new TypeClassFactory();
14: TypeClass typeobj = factory.GetTypeClass(name);
15: return typeobj;
16: }
17:
18: void IUserType.NullSafeSet(IDbCommand cmd,
19: object value,
20: int index) { 21:
22: string name = ((TypeClass)value).Name;
23: NHibernateUtil.String.NullSafeSet(cmd, name, index);
24: }
25: }
Having created this class, I can now explicitly map the association between ActualClass and TypeClass as a simple property on the ActualClass mapping.
<property
name="Type"
column="TypeName"
type="Samples.NHibernate.DataAccess.TypeClassUserType,
Samples.NHibernate.DataAccess" />
As NHibernate is in the process of saving an instance of ActualType, it will load and create a new instance of TypeClassUserType and call the NullSafeSet method. As you can see from the method body, I am simply extracting the name from the mapped property (passed in as the value parameter) and setting the extracted name as the value of the parameter to be set in the database. The net result is that although the Type property of ActualClass is TypeClass in the domain model, only the Name property of the TypeClass object gets stored in the database. The converse is also true. When NHibernate is loading an instance of ActualType from the database and the finds a property of my custom mapping type, it loads my custom type and calls the NullSafeGet method. As you can see, my method gets the name from the returned data, calls my flyweight factory to get the correct instance of TypeClass, and then actually returns that instance. The type resolution process happens transparently to my data access classes (and even to NHibernate itself for that matter).
Scenario 2:
As a part of your object model, you have a standard IDictionary implementation (Oh my goodness – untyped collections!!! Yea – it still happens sometimes!) that looks as follows.
private IDictionary _values = new Hashtable();
Now, here’s the basic problem with this implementation when persisting to a database. By design, this data structure can contain – well – anything. So how do you persist it to the database in a meaningful way? One technique that can be used is to store the value as a string in one column and store the type identifier as a string in a different column. The problem here is that many times (especially when you think about it from the database point of view) you end up with a clunky object model because there is no real reason to store the type as an additional piece of data. Fundamentally, the database requires 2 pieces of information to make sense of 1 piece of domain model information (value + type). It would be really great if we could hide this splitting and merging of data in the mapping layer…
…and we can – using a composite custom mapping type. Take a look at the following.
1: public class Object2StringUserType : ICompositeUserType
2: { 3:
4: ...
5:
6: object ICompositeUserType.NullSafeGet(IDataReader dr,
7: string[] names,
8: ISessionImplementor session,
9: object owner) { 10:
11: string val = (string) NHibernateUtil.String.NullSafeGet(dr,
12: names[0],
13: session,
14: owner);
15:
16: string valTypeString =
17: (string) NHibernateUtil.String.NullSafeGet(dr,
18: names[1],
19: session,
20: owner);
21:
22: if (val == null || valTypeString == null)
23: return null;
24:
25: Type valType = Type.GetType(valTypeString);
26: return TypeDescriptor.GetConverter(valType).ConvertFrom(val);
27: }
28:
29: void ICompositeUserType.NullSafeSet(IDbCommand cmd,
30: object value,
31: int index,
32: ISessionImplementor session) { 33:
34: string valString = TypeDescriptor.GetConverter(
35: value.GetType()).ConvertToString(value);
36:
37: string valTypeString = value.GetType().AssemblyQualifiedName;
38: NHibernateUtil.String.NullSafeSet(cmd, valString, index, session);
39: NHibernateUtil.String.NullSafeSet(cmd,
40: valTypeString, index + 1, session);
41: }
42:
43: string[] ICompositeUserType.PropertyNames { 44: get { return new string[] {"val", "valType"}; } 45: }
46: }
As you can see, this interface fragment looks similar to that of our regular custom mapping type. However, it has several additional members which allow the custom type to actually map a given domain model value onto multiple database columns. In this example, I have used the type to split the value of my IDictionary object over 2 columns in my database table: Value and ValueType. When my dictionary is persisted, my mapping type converts the dictionary value to a string and then persists that value along with the fully qualified name of the value’s original type to 2 columns in the database. Those columns are identified in NHibernate mapping file and mapped to property names (in this case val and valType) based on the order in which they appear in the mapping file. The NHibernate mapping XML looks as follows.
<map name="_values" access="field" table="Values">
<key column="ID_Parent"></key>
<index column="Key" type="String"></< SPAN>index>
<element type="Samples.NHibernate.DataAccess.Object2StringUserType,
Samples.NHibernate.DataAccess">
<column name="Value" />
<column name="ValueType"/>
</element>
</map>
Again, all of this happens behind the scenes as NHibernate is setting and fetching your objects from the database. All you know is that for any type that you put into the your dictionary, you can expect the same type to be in the dictionary for that item, even after saving and loading it from the database.
In conclusion, I hope that this has illustrated the flexibility that can be found within NHibernate as it relates to some more advanced mapping scenarios. If you have any questions or ideas for custom mapping types, please share!
I am currently the Editor-in-Chief for MSDN Magazine. I joined Microsoft in 2006 as a product planner with the certification team at Microsoft Learning. Prior to that, I spent my career as a developer and later as an architect. My main technology passions include pretty much anything on language theory, agile development, and service-oriented architecture.