Last time around
Last time we looked at how we map inheritance in NHibernate. This time I want to look at value types.
Mapping Value types
NHibernate supports a ‘fine-grained object model’. That is to say it allows us to map value types as well as entities.
All ORM tools let you map between a class and a table. This provides us with the support we need to map the entities in our domain model. Entities are distinguished by having a unique identifier throughout their lifetime; that is to say we compare entities by identity, not by value. As an example, the tax system needs to continue to find you, even if you change your name, so you will have some form of tax number that acts as your unique identifier. The mapping used is straightforward. Because each row in a Db table needs a unique identifier (the primary key) a row can be seen as equivalent to an instance of an entity.
It’s obvious that all ORMs need to support mapping the primitive types (int, string, bool, etc) that you comprise your entity from. Again the principle is simple, each field or property on your entity maps to a column in the table.
Less well understood is that primitive types are just one class of types that we call value types – that is types that we compare by value. When we build our object model we may find a host classes in our domain that should be compared by value instead of by identity.
The canonical example here is money. When we record a monetary value we may want to know two things – the amount, and the currency. When we compare for equality between two monetary amounts we tend to compare that they have the same currency and amount. In an OO design we want to keep the two concepts, value and amount, together so we create a class called Money.
Within the Db we would tend to represent money and amount as two columns, within the entity, such as a purchase order line item, we were recording the cost of. If our ORM only supports mapping entities though we will find that the only way to represent money and have it mapped to two columns on the LineItem table is to explicitly represent the monetary amount as a amount field and a currency field within the LineItem. We cannot use Money, because the only way that our ORM knows to map a class is as an entity, so we would have the somewhat perverse scenario of having a foriegn key lookup to the Money table to find the cost.
What we want is to be able to map a value type to a number of columns on a table. (You don’t have to map a value type this way, but it makes the most sense in most cases). NHibernate allows you to map user-defined value types or collections of value types. We won’t show the latter here, as it’s not really an introductory topic, but we will talk about the former.
NHibernate uses the term component for a user-defined type that is mapped to columns in the row of another entity. We use the <component/> element to map the value type on the <class/>.
Consider, for example, that we might want to map the Customer’s FirstName and SurName as a Name type. (We are using the same entities that we provided earlier in the series). We could alter the class definition as follows:
public class Name
{
public string FirstName {get;set;}
public string Surname {get;set;}
}
public class Customer
{
public Company Company { get; set; }
public DateTime DateOfBirth { get; set; }
public int Id { get; set; }
public Name Name { get; set; }
public int Version { get; set; }
}
Then we can map it as follows:
<class name=”Customer” table=”Customer”>
<id name=”Id” type=”Int32″ unsaved-value=”0″>
<generator class=”identity”>
</generator>
</id>
<version name=”Version”></version>
<property name=”DateOfBirth”></property>
<component name=”Name”></component>
<property name=”FirstName”></property>
<property name=”Surname”></property>
</component>
<many-to-one name=”Company” column=”CompanyId” cascade=”all-delete-orphan”></many-to-one>
</class>
And that is all there is to it.
User Defined Mappings
NHibernate also has support for providing custom mapping for any type, allowing you to take control of how objects are persisted and materialized. In some cases you may want to use this to take more control of your mappings. To add a custom mapping you need to create a class that derives from IUserType (for single field types) or ICompositeUserType (for types with multiple fields). Custom mapping is too large in scope for this article to cover completely, butwe’ll try to cover the basics.
Let us assume that we want to map our Name above using a composite user type instead of a component
We create a new type NameUserType and implement the ICompositeUserType interface. Essentially NHibernate calls our composite user type to materialize or persist instances of our value type. Those of you who have written ADO.Net persistence code for a data reader by hand will probably recognize how this works.
public class NameUserType : ICompositeUserType
{
private static readonly SqlType[] sqlTypes = new SqlType[] {NHibernateUtil.String.SqlType, NHibernateUtil.String.SqlType};
public object GetPropertyValue(object component, int property)
{
var name = (Name) component;
if (property == 0)
return name.Firstname;
else
return name.Lastname;
}
public void SetPropertyValue(object component, int property, object value)
{
//our value type is immutable, we create a new instance instead of modifying
throw new InvalidOperationException(“Name is immutable”);
}
public bool Equals(object x, object y)
{
if (object.ReferenceEquals(x,y)) return true;
if (x == null || y == null) return false;
//our type should know how to compare itself
return x.Equals(y);
}
public int GetHashCode(object x)
{
//our type should provide a hashcode
return x.GetHashCode();
}
public object NullSafeGet(IDataReader dr, string[] names, ISessionImplementor session, object owner)
{
var firstName = NHibernateUtil.String.NullSafeGet(dr, names[0]);
var lastName = NHibernateUtil.String.NullSafeGet(dr, names[1]);
return new Name
{
Firstname = firstName,
Lastname = lastName
};
}
public void NullSafeSet(IDbCommand cmd, object value, int index, ISessionImplementor session)
{
if (value == null)
{
((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
((IDataParameter)cmd.Parameters[index+1]).Value = DBNull.Value;
}
else
{
var name = (Name)value;
((IDataParameter)cmd.Parameters[index]).Value = (object)name.Firstname ?? DBNull.Value;
((IDataParameter)cmd.Parameters[index + 1]).Value = (object)name.Lastname ?? DBNull.Value;
}
}
public object DeepCopy(object value)
{
//As an immutable value type, just return the argument.
//If we were an entity we would need to deep copy
return value;
}
public object Disassemble(object value, ISessionImplementor session)
{
//write to 2nd level cache
return value;
}
public object Assemble(object cached, ISessionImplementor session, object owner)
{
//read from 2nd level cache
return cached;
}
public object Replace(object original, object target, ISessionImplementor session, object owner)
{
//we are immutable
return original;
}
public string[] PropertyNames
{
//What are the names of our user types properties
get { return new string[] {“Firstname, Lastname”};}
}
public IType[] PropertyTypes
{
//What are the types of our user types properties
get { return new IType[] {NHibernateUtil.String, NHibernateUtil.String}; }
}
public Type ReturnedClass
{
get { return typeof (Name); }
}
public SqlType[] SqlTypes
{
get { return sqlTypes; }
}
public bool IsMutable
{
//we are immutable
get { return false; }
}
}
If we can map Name by both a component and a user type why would we ever want to use a user type? One answer is simply to save duplication in our mapping files. But a more complex answer is that you may want to map a family of types through an interface, that all share the same state, but have differing behaviour. You need to distinguish which concrete type NHibernate should use to materialize this instance from the store. You can write code within your user type to manage that.
The key takeaway is to recognize that NHibernate supports a rich domain model better ORMs, for example Linq To Sql, that do not have support for mapping value types.