Wednesday, January 14, 2009

Eager loading from a formula in NHibernate

A little while ago, our project started looking at optimizing some of our code. Just for some background the application is a windows based smart client using NHibernate, WCF, WPF and a custom built framework tying them together.

One of the problems I faced was the disparity between our relational model and object model, and so leveraging our ORM we use formulas and mappings to bridge the gap between the database and our entities. The next problem was that we mapped the ID's of disconnected entities onto the entity in question using formulas, meaning we had to lazy load (over WCF) the sub-entities mapping using a formula. In some cases this is perfectly fine, in others it proved a bottleneck and we wanted to get everything loaded in one network call and database hit.

At the time of writing (coding?) NHibernate did not support mapping a formula to an object. We would expect something such as:

<property name="Entity" formula="dbo.Formula(ID)" class="Namespaces.Foo, SampleAssembly" />

Unfortunately this doesn't work, so I had to create a custom user type for it. If you don't know about NHibernate UserTypes I suggest you read: Ayende: UserTypes.

So now the XML to eager load our entity looks as below :-

<property name="Call" formula="dbo.PersonCurrentCall(ID)">
  <type name="Namespace.EagerLoadingUserType, Assembly">
    <param name="entitytype">
      Entities.TypeOfEntityToEagerLoadFromID, Assembly
    </param>
  </type>
</property>

The code for the user type is below however the magic is really in the IParameterizedType interface. This effectively allows you to give your UserType arguments (in my case the fully qualified class name of the entity we wish to eager load. SetParameterValues is called when NHibernate is first loaded and reads the mapping data.

The NullSafeGet method contains some of our domain specific code which you will need to replace with your own. When NHibernate loads up the root entity (which our "TypeOfEntityToEagerLoadFromID" hangs off) it will attempt to populate the entities members, and will hit our NullSafeGet method with the ID for this member. This is the point which you will want to use your server-side container or framework to load this new entity from your datasource (probably using NHibernate) and return the object for NHibernate to insert into the root entity.

/// <summary>
/// Eager loads entities which are mapped using a SQL formula.
/// </summary>
public class EagerLoadingUserType  IUserType, IParameterizedType
{
    private Type _type;
    
    #region IParameterizedType Members
    
    /// <summary>
    /// Sets the parameter values. In this case the type of entity 
    /// we are eager loading.
    /// </summary>
    /// <param name="parameters">The parameters.</param>
    public void SetParameterValues(IDictionary parameters)
    {
        _type = Type.GetType((string)parameters["entitytype"]);
    }

    #endregion

    #region IUserType Members
    
    /// <summary>
    /// Gets the type and ID of the object load eager load from the 
    /// database.
    /// </summary>
    /// <param name="rs">The rs.</param>
    /// <param name="names">The names.</param>
    /// <param name="owner">The owner.</param>
    /// <returns></returns>
    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        object r = rs[names[0]];
        int? entityID = (int?) r;
        if(entityID.HasValue && _type.IsAssignableFrom(typeof(EntityBase)))
        {
            return FrameworkHelper.LoadEntity(_type, entityID);
        }

        return null;
    }

    #endregion
}

A large amount of this class has been omitted since it implements the interface based on the defaults outlined in Ayende's article.

I hope you have found this post useful, I've tried to explain as best I can and I may put together some working sample code in the future. This may also not be the best approach but it was the only one I could see that worked.

No comments:

Post a Comment