Introduction to NHibernate, pt.5

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.

 

About Ian Cooper

Ian Cooper has over 18 years of experience delivering Microsoft platform solutions in government, healthcare, and finance. During that time he has worked for the DTi, Reuters, Sungard, Misys and Beazley delivering everything from bespoke enterpise solutions to 'shrink-wrapped' products to thousands of customers. Ian is a passionate exponent of the benefits of OO and Agile. He is test-infected and contagious. When he is not writing C# code he is also the and founder of the London .NET user group. http://www.dnug.org.uk
This entry was posted in NHibernate, Object-Orientation, ORM. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://www.nfljerseys2u.net Minnesota Vikings Jerseys

    Dear friends, we are an international trade company,which specializes in NFL jerseys. You can buy cheap Minnesota Vikings Jerseys . Welcome to visist here .

  • http://www.nfljerseys2u.net Minnesota Vikings Jerseys

    Dear friends, we are an international trade company,which specializes in NFL jerseys. You can buy cheap Minnesota Vikings Jerseys . Welcome to visist here .

  • http://www.buyrunegp.com runescape money

    Or you can buy runescape money.

  • http://www.goldsrunescape.com runescape

    mapping value types and user defined types . This time I want http://www.buyrunescape2.com

  • http://www.ibay24.com metin2 yang

    metin2 yang
    metin2 yang

  • http://www.ibay24.com metin2 yang

    Turn right after the plaza across the immortal, metin2 yang

  • http://www.sopgame.com wowgold

    Europa League (formerly the UEFA Cup) third round of group stage 22,http://www.inwowgold.com

  • http://gvlmlpia.com/ Krypeqjk

    unJQLF

  • http://www.g2gmart.com runescape gold

    @ -=- @ ==**====**=
    runescape power levelingClearly about the changes between Death Knight, please visit a the wow-lvl website specialized in selling wow gold. runescape goldwarcraft gold Items. runescape goldThey are able to provide a world of warcraft gold service at first time when it came out. runescape accountsand wow gold service. The price of Buy Wow Gold for the two new races is as the same as the original races. It is an unimaginable price. runescape goldwow powerleveling service. welcome to Compared with other wow gold suppliers, we are in advantage of selling the cheapest wow gold.
    ★* ★ ★