Geeks With Blogs
// ThomasWeller C#/.NET software development, software integrity, life as a freelancer, and all the rest

This series of posts is about overcoming a restriction, that O/R mappers like NHibernate have with respect to lazy loading and polymorphic type information. (Please refer to the problem description and example in part 1 and 1.5.) The previous part of this series demonstrated how we can fetch type-discriminating data from the db during the regular insert/update/retrieve lifecycle of an instance, along with its 'normal' data, and totally transparent from the domain perspective. This part now will show how we can make use of this data to make NHibernate generate a strongly-typed lazy-loading proxy - instead of a polymorphic one, which can lead to some pretty sophisticated complications, as the examples demonstrate.

So where are we? Currently, we have an 'entity' (NH's notion of an in-memory object  that relates to a certain domain object) with a 'hidden' type discriminator value. Now we need to exploit that value to make NH generate a dynamic proxy of the desired type.

To do that, we first need to know something about the extension points that NH provides for hooking into the object creation process. Basically, there are two different mechanisms, namely Interceptors and Events, as this knowledge article describes in some more detail. Additionally, we have to keep in mind that NH entities are undergoing a two-phased loading process:

  • During the first step, the raw instance data are fetched from the db - including possible reference data like the BOOKS.AUTHOR column in our example, which is a foreign key reference to a row in the AUTHORS table.
  • In a second pass, these relational data will then be converted to their actual object references which will finally surface on the domain model. During that step, NH creates dynamic proxies as needed. (Well, this is by far not the only thing that's happening in the second phase, it's just the here relevant piece. If you'd like to get a deeper view on that and on how these operations may influence NH's performance, I recommend you this post. - Many of the complaints about NH's performance come from improper understanding of this step!)

 

Hooking into NH's loading process

To interfere with the proxy creation process, we will listen to NH's LoadEvent event. The standard  DefaultLoadEventListener class is NH's implementation of the loading process and is invoked without any configuration. It is a pretty complex beast that coordinates the entire loading phase. Among many other things, it calls itself recursively for proxy creation with the 'InternalLoadLazy' load type. This is where we need to step into the process with the below:

public class PolymorphicResolvingLoadListener : ILoadEventListener

{

    public void OnLoad(LoadEvent @event, LoadType loadType)

    {

        if (loadType == LoadEventListener.InternalLoadLazy)

        {

            IPersistenceContext persistenceContext = @event.Session.PersistenceContext;

 

            // get the entity that is currently undergoing the two-phase load process

            EntityEntry loadingEntityEntry = persistenceContext.GetLoadingEntityEntry();

 

            if (loadingEntityEntry != null)

            {

                IEntityPersister loadingEntityPersister = loadingEntityEntry.Persister;

 

                for (int i = 0; i < loadingEntityPersister.PropertyNames.Length; i++)

                {

                    EntityType type = loadingEntityPersister.PropertyTypes[i] as EntityType;

 

                    if (type == null || type.Name != @event.EntityClassName)

                    {

                        continue;   // this is not an entity at all or an entity of

                    }               // the 'wrong' type, so skip it

 

                    // create the expected name for the discriminator property

                    string discriminatorPropertyName =

                        string.Format("{0}__discriminator__", loadingEntityPersister.PropertyNames[i]);

 

                    // get the currently loaded value - can be an ID or an INHibernateProxy instance

                    object loadedValue =

                        loadingEntityEntry.GetLoadedValue(loadingEntityPersister.PropertyNames[i]);

 

                    if (!(loadingEntityPersister.PropertyNames.Contains(discriminatorPropertyName)) ||

                        loadedValue is INHibernateProxy)

                    {

                        continue;   // this property has no related discriminator property,

                    }               // or it has already a proxy assigned, so skip it

 

                    // now get the actual discriminator value...

                    object discriminatorValue =

                        loadingEntityEntry.GetLoadedValue(discriminatorPropertyName);

 

                    // ...and replace the entity class name accordingly. NH will create

                    // a dynamic proxy for the here provided type.

                    @event.EntityClassName =

                        InheritanceMap.GetSubclassFor(type.Name, discriminatorValue);

 

                    break;

                }

            }

        }

    }

} // class PolymorphicResolvingLoadListener

As you can see, we don't have to soil our hands with creating a proxy ourselves. Rather we simply tell NH what exact class we want a proxy for, and NH will do all the rest for us. To make this happen, we only need to change the value of the EntityClassName property of the LoadEvent argument accordingly, after we have accessed and evaluated our 'hidden' type discriminator value.

To finally put our custom event listener into action, we need a configuration entry telling NH to invoke it - in addition to the NH default listener:

<session-factory>

  ...

  <event type="load">

    <listener class="LLDemo.Persistence.PolymorphicResolvingLoadListener, LLDemo.Persistence"/>

    <listener class="NHibernate.Event.Default.DefaultLoadEventListener, NHibernate"/>

  </event>   

</session-factory>

Alternatively, you can do this programmatically: 

ILoadEventListener[] listenerStack = new ILoadEventListener[]

    {

         new PolymorphicResolvingLoadListener(),

         new DefaultLoadEventListener()

    };

configuration.EventListeners.LoadEventListeners = listenerStack;

 

Exploiting the configuration metadata

You may have noticed the invocation of the InheritanceMap class at the end of the above shown OnLoad() method. This helper is used to find the correct subclass for a given superclass/discriminator pair. It is initialized during the application's initialization phase, together with NHibernate's own initialization. The respective call looks like this:

var configuration = new Configuration().Configure();

InheritanceMap.Initialize(configuration);

During initialization, NHibernate creates a very rich meta-level model of the mapped domain and all the various details that are needed to do the actual mapping later on. The InheritanceMap class evaluates these data and extracts the relevant information from it. A possible implementation of the Initialize() method would be:

public static void Initialize(Configuration configuration)

{

    if (configuration == null)

    {

        throw new ArgumentNullException("configuration");

    }

 

    map = new Dictionary<string, string>();

 

    PersistentClass[] subclasses = configuration.ClassMappings

        .Where((@class) => @class.IsPolymorphic &&

                           @class.Superclass != null &&

                           !string.IsNullOrEmpty(@class.DiscriminatorValue))

        .Cast<PersistentClass>()

        .ToArray();

 

    foreach(var subclass in subclasses)

    {

        map.Add(GetKeyFor(subclass.Superclass.EntityName,

                          subclass.DiscriminatorValue),

                subclass.EntityName);

    }

 

    isInitialized = true;

}

 

The provided demo solution

To see all this in more detail, please refer to the provided sample solution (VS 2008). It includes not only the full source code, but also a db script to create the necessary sample data. The script and the NH configuration settings assume that the code is running against a MS SQL server instance - you will need to adjust the connection string in the App.config file to target your specific environment settings.

Furthermore, I should make the disclaimer that this sample is not intended to be a full, all-purpose solution for the here discussed issues. For example, it doesn't take into account multi-level inheritance hierarchies, and it also doesn't handle collections.

In addition to the here described event listening and configuration code, the solution also makes use of NH's new capability to invoke an IoC container for object/proxy creation (I used the Castle/Windsor container here). This overcomes another restriction that you usually buy with NHibernate: Namely that you have to provide a default constructor for your domain objects and that you have to make almost everything virtual. While that may go almost unconsciously for experienced NH users, here again the persistence system dictates how to write domain code, ultimately undermining the concept of Persistence ignorance. But that's left to the next part...

Posted on Saturday, October 3, 2009 5:57 AM NHibernate , Architecture and Design | Back to top


Comments on this post: Lazy loading, Inheritance, and Persistence ignorance (part 3)

# re: Lazy loading, Inheritance, and Persistence ignorance (part 3)
Requesting Gravatar...
Hi

What's the license for your example code? Your zip file asserts your copyright, but no license. Can you clarify if one can use this code in other projects?
Left by Jim on Oct 13, 2009 10:45 PM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 3)
Requesting Gravatar...
Uups, sorry fort that.

The file headers are generated automatically for each new file (ReSharper template). The contained copyright notice is originally intended for a commercial context - I just didn't think of it for the samples. You can use the sample code in any way you like, and I will remember to take care of this issue for the next code samples ;-).
Left by Thomas Weller on Oct 14, 2009 4:53 AM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 3)
Requesting Gravatar...
Nice articles. But I'm curious: if you're going to the related table to pull out the discriminator anyhow, why bother with lazy loading? If you're joining against the authors table anyhow for the load, I'm assuming that most of the performance benefit of lazy loading is lost, and you may as well load the entire associated property.

I'd love to hear your thoughts.
Left by Remi Despres-Smyth on Dec 23, 2009 3:01 PM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 3)
Requesting Gravatar...
Remi,

thanks for the kind words.
What you're saying is right, at least it sounds plausible enough (I didn't make any real performance tests...).

I never thought of the issue in terms of performance, but more in terms of a database with many tables that are highly interconnected. With eager loading, you would end up loading half the database in the end, if you only wanted a single entity. This was always the important thing for me with respect to lazy loading, not the immediate performance issue (which is in many cases simply irrelevant).

- Thomas
Left by Thomas Weller on Dec 23, 2009 6:00 PM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 3)
Requesting Gravatar...
So the benefit then isn't so much in the isolated case of one class with a related polymorphic property, but in the case where your object graph spans much further than that - it would allow you to stop the load with a single class + one join to a related class.
Left by Remi Despres-Smyth on Dec 23, 2009 6:07 PM

# re: Lazy loading, Inheritance, and Persistence ignorance (part 3)
Requesting Gravatar...
Yup, exactly...
Left by Thomas Weller on Dec 23, 2009 7:33 PM

Your comment:
 (will show your gravatar)


Copyright © Thomas Weller | Powered by: GeeksWithBlogs.net