Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Lazy Initializer for NHibernate

0.00/5 (No votes)
4 Mar 2009 1  
Reducing StaleObjectStateException damage in multi-user WinForms applications.

Introduction

First and foremost, I would like to say that this article is based on the work and ideas of Steinar Dragsnes, who is behind most of the code here. Needless to say, without his consent, this article wouldn't have seen the light of day.

Now, those of us who have worked on a WinForms application using NHibernate might have came across the problematic issue of session management and the oh so well known LazyInitializationException. The idea is that on a WinForms application, you tend not to leave an open session during the lifetime of your View, as it could last for a far too long time. This means you might easily come across the LazyInitializationException whenever you are accessing a lazy loaded proxy. This article will present an idea for a lazy initializer static class.

Background

NHibernate session management in WinForms applications is rather complicated when compared to that of web applications, the reason being not all sessions can be left open for the whole lifetime of a form, or for that matter, for longer than the duration of a service method. The main reason would be to simply disallow the NHibernate session from getting too heavy (with data and on your memory) and out of control; another good reason is the StaleObjectStateException that you might need to handle, especially when working with a multi-user system where simultaneous write/update operations may occur on a regular basis (and thanks again to Steinar for making that point). That's how we get the LazyInitializationException issue - whenever a lazy collection or proxy is accessed, since the session is not opened, the exception is thrown. This means that you'll need to anticipate trips to the database in order to initialize these lazy entities. One of the ways to handle that would be to have initializing methods in your DAOs. The idea behind the code I will present here is a static class that would take care of initializing these entities, and if needed, will initialise their lazy properties and the properties of the properties, and so on.

Some more background on the subject can be found in my earlier article on NHibernate for WinForms, where I've tried to assemble as much data on the subject as possible.

Using the Code

Now, to get started using the following class, there are a couple of methods you'll need to understand - the ctor LazyInitializer, and the alternative ctor AlternativeCOnstructor.

The ctor - the static LazyInitializer() - uses NHibernate.Mapping.Attribute to get all the mapped classes with lazy properties. The method is fairly documented, and there's nothing much to add. The AlternativeConstructor method uses the NHibernate.Cfg.Configuration class to get all the mapped domain objects with lazy properties - the big plus about this method is that it can be used on any kind of mapping system (HBM files, Mapping.Attribute, or NHibernate Fluent Interfaces); though, you'll need to add a new query for any lazy property that you might use (I provided queries only for Bag and many/one-to-one attributes), whereas the other method (using Mapping.Attribute) takes care of all the possible lazy properties/attributes. (I should also add a method for NFI).

These methods create a dictionary of Type (as key) and a list of PropertyInfo or MethodInfo (as value). This dictionary will then be used in the ExtractNHMappedProperties method in order to initialize the lazy properties of a certain entity. We get the lazy properties by the entity type - if our dictionary contains the entity type, we have at least one lazy property...

The method is called recursively so that we can drill into the entity tree as far as we wish. The LazyInitialise method is the one that does the actual initializing of the entity/property with NHibernateUtil.Initialize(proxy);. The InitializeEntity, InitializeCompletely, and ImmediateLoad methods are public, and are used to communicate with and be called from the DAO to initialize the entity.

The other thing I do is build a list of all the mapped classes declared as proxies, so that when the DAO loads a sole entity of the proxy class (Session.Load<MyProxyClass>(1);), I initialize the proxy. The reason I do that is because I use a generic parent class for all my DAOs; and, loading my entities, I don't know which type is loaded, so I always check whether the entity is of a proxy type, and if so, I initialize the entity. If you don't use a generic parent DAO class, there's no need to do that.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Collection;
using NHibernate.Mapping.Attributes;
using NHibernate.Proxy;
using LockMode=NHibernate.LockMode;

namespace MyApplication.Data
{
    public static class LazyInitializer
    {
        #region fields
        /// <summary>
        /// A container of all types containing lazy properties
        /// with a list of their respective lazy properties.
        /// </summary>
        private static IDictionary<Type, List<PropertyInfo>> 
                propertiesToInitialise = 
                new Dictionary<Type, List<PropertyInfo>>();
        private static IList<Type> proxyTypes = new List<Type>();
        #endregion
        
        /// <summary>

        /// Fully initialize the instance of T type with a primary key equal to id.
        /// </summary>
        /// <typeparam name="T">The type to resolve and load</typeparam>
        /// <param name="id">The primary key of the type to load</param>

        /// <param name="session">The session factory
        /// used to extract the current session</param>
        /// <returns>The fully initialized entity</returns>
        public static T ImmediateLoad<T>(int id, ISession session)
        {
            T entity = session.Load<T>(id);
            return InitializeCompletely(entity, session);
        }

        /// <summary>

        /// Convenience method for loading the complete
        /// object graph for an already initialized entity,
        /// where parts of the entity's object graph may be proxy instances.
        /// </summary>
        /// <remarks>
        /// This is done by recursively looping through all NHibernate
        /// mapped properties in the object graph and 
        /// examining if they are lazy loaded (represented by a proxy).
        /// It is at this moment unknown
        /// whether this approach is inefficient. This must be tested.
        /// </remarks>
        /// <param name="entity">

        /// The entity to initialize. This must be an initialized
        /// entity object that holds lazy properties. From
        /// the LazyInitializer's scope, the entity is the top node in the object graph.
        /// </param>
        /// <param name="session">The current session</param>
        /// <returns>The fully initialized entity</returns>

        public static T InitializeCompletely<T>(T entity, ISession session)
        {
            // Okay, first we must identify all the proxies we want to initialize:
            ExtractNHMappedProperties(entity, 0, 0, true, session);
            return entity;
        }

        /// <summary>
        /// Convenience method for loading proxies in entity
        /// object graph. Providing fetch depth to speed up
        /// processing if only a shallow fetch is needed.
        /// </summary>
        /// <remarks>
        /// This is done by recursively looping through
        /// all properties in the object graph and 
        /// examining if they are lazy loaded (represented
        /// by a proxy). It is at this moment unknown
        /// wether this approach is inefficient. This must be tested.
        /// </remarks>

        /// <param name="entity">
        /// This is done by recursively looping through
        /// all NHibernate mapped properties in the object graph and 
        /// examining if they are lazy loaded (represented
        /// by a proxy). It is at this moment unknown
        /// wether this approach is inefficient. This must be tested.
        /// </param>
        /// <param name="maxFetchDepth">The search depth.</param>
        /// <param name="session">The current session</param>

        /// <returns>A partly initialized entity,
        /// initialized to max fetch depth</returns>
        public static T InitializeEntity<T>(T entity, 
                        int maxFetchDepth, ISession session)
        {
            // Let's reduce the max-fetch depth to something tolerable...
            if (maxFetchDepth < 0 || maxFetchDepth > 20) maxFetchDepth = 20;
            // Okay, first we must identify all the proxies we want to initialize:
            ExtractNHMappedProperties(entity, 0, maxFetchDepth, false, session);
            return entity;
        }

        /// <summary>
        /// Search the object graph recursively for proxies,
        /// until a certain threshold has been reached.
        /// </summary>

        /// <param name="entity">The top node in the object
        /// graph where the search start.</param>
        /// <param name="depth">The current depth from
        /// the top node (which is depth 0)</param>
        /// <param name="maxDepth">The max search depth.</param>

        /// <param name="loadGraphCompletely">Bool flag indicating
        /// whether to ignore depth params</param>
        /// <param name="session">The current session to the db</param>
        private static void ExtractNHMappedProperties(object entity, int depth, 
                       int maxDepth, bool loadGraphCompletely, ISession session)
        {
            bool search;
            if (loadGraphCompletely) search = true;
            else search = (depth <= maxDepth);

            if (null != entity)
            {
                // Should we stay or should we go now?
                if (search)
                {
                    // Check if the entity is a collection.
                    // If so, we must iterate the collection and
                    // check the items in the collection. 
                    // This will increase the depth level.
                    Type[] interfaces = entity.GetType().GetInterfaces();
                    foreach (Type iface in interfaces)
                    {
                        if (iface == typeof(ICollection))
                        {
                            ICollection collection = (ICollection)entity;
                            foreach (object item in collection) 
                              ExtractNHMappedProperties(item, depth + 1, 
                                maxDepth, loadGraphCompletely, session);
                            return;
                        }
                    }

                    // If we get here, then we know that we are
                    // not working with a collection, and that the entity
                    // holds properties we must search recursively.
                    // We are only interested in properties with NHAttributes.
                    // Maybe there is a better way to specify this
                    // in the GetProperties call (so that we only get an array
                    // of PropertyInfo's that have NH mappings).
                    List<PropertyInfo> props = propertiesToInitialise[entity.GetType()];
                    foreach (PropertyInfo prop in props)
                    {
                        MethodInfo method = prop.GetGetMethod();
                        if (null != method)
                        {
                            object proxy = method.Invoke(entity, new object[0]);
                            if (!NHibernateUtil.IsInitialized(proxy))
                            {
                                LazyInitialise(proxy, entity, session);
                            }

                            if (null != proxy)
                              ExtractNHMappedProperties(proxy, depth + 1, maxDepth, 
                                         loadGraphCompletely, session);
                        }
                    }
                }
            }
        }

        /// <summary>

        /// The core method delegating the hard lazy initialization
        /// work to the hibernate assemblies.
        /// </summary>
        /// <param name="proxy">The proxy to load</param>
        /// <param name="owner">The owning
        /// entity holding the reference</param>

        /// <param name="session">The current session to the db</param>
        private static void LazyInitialise(object proxy, object owner, ISession session)
        {
            if (null != proxy)
            {
                Type[] interfaces = proxy.GetType().GetInterfaces();
                foreach (Type iface in interfaces)
                {
                    if (iface == typeof (INHibernateProxy) || 
                        iface == typeof (IPersistentCollection))
                    {
                        if (!NHibernateUtil.IsInitialized(proxy))
                        {
                            if (iface == typeof (INHibernateProxy))
                                session.Lock(proxy, LockMode.None);
                            else //if (session.Contains(owner)) 
                                session.Lock(owner, LockMode.None);

                            NHibernateUtil.Initialize(proxy);
                        }

                        break;
                    }
                }
            }
        }

        #region ctor

        /// <summary>
        /// An alternative approach to initializes the
        /// <see cref="LazyInitializer"/> class.
        /// </summary>

        /// <remarks>
        /// this method should be called after
        /// the NH Cfg.Configuration object has been configured
        /// and before cfg.BuildSessionFactory(); has been called!
        /// This might be more demanding and difficult
        /// for those who work with DI tools. On the other hand
        /// this approach will work for ANY kind of mapping:
        /// Mapping.Attribute, Hbm and even NHibernate Fluent Interfaces.
        /// </remarks>
        public static void AlternativeConstructor()
        {
            var cfg = new Configuration();
            // get all types (with their lazy props) having lazy 
            // many/one-to-one properties
            var toOneQuery = from persistentClass in cfg.ClassMappings
                             let props = persistentClass.PropertyClosureIterator
                             select new { persistentClass.MappedClass, props }
                                 into selection
                                 from prop in selection.props
                                 where prop.Value is NHibernate.Mapping.ToOne
                                 where ((NHibernate.Mapping.ToOne)prop.Value).IsLazy
                                 group selection.MappedClass.GetProperty(prop.Name) 
                                       by selection.MappedClass;
            // get all types (with their lazy props) having lazy nh bag properties
            var bagQuery = from persistentClass in cfg.ClassMappings
                           let props = persistentClass.PropertyClosureIterator
                           select new { persistentClass.MappedClass, props }
                               into selection
                               from prop in selection.props
                               where prop.Value is NHibernate.Mapping.Collection
                               where ((NHibernate.Mapping.Collection)prop.Value).IsLazy
                               group selection.MappedClass.GetProperty(prop.Name) 
                                     by selection.MappedClass;
            // TODO: add queries of any other
            // mapping attribute you use that might be lazy.

            foreach (var value in toOneQuery)
                propertiesToInitialise.Add(value.Key, value.ToList());
            foreach (var value in bagQuery)
            {
                if (propertiesToInitialise.ContainsKey(value.Key))
                    propertiesToInitialise[value.Key].AddRange(value.ToList());
                else
                    propertiesToInitialise.Add(value.Key, value.ToList());
            }
            // TODO: add treatment of any other mapping
            // attribute you use that might be lazy.
        }

        /// <summary>

        /// Initializes the <see cref="LazyInitializer"/> class.
        /// </summary>
        static LazyInitializer()
        {
            // NOTE: you may prefer to pass assemblies as parameters.
            Assembly asm = Assembly.GetAssembly(typeof(MyApplication.Domain.IDomainObject));
            Type[] types = asm.GetTypes();
            foreach (Type type in types)
            {
                List<PropertyInfo> propertyInfos = new List<PropertyInfo>();
                object[] classAttributes = type.GetCustomAttributes(
                                              typeof(ClassAttribute), false);
                object[] joinedSubclassAttribute = 
                         type.GetCustomAttributes(typeof(JoinedSubclassAttribute), 
                    false);
                if (classAttributes.Length > 0 || joinedSubclassAttribute.Length > 0)
                {
                    AddProxies(type, classAttributes, joinedSubclassAttribute);

                    PropertyInfo[] properties = type.GetProperties();
                    foreach (PropertyInfo property in properties)
                    {
                        bool isLazy = false;

                        // Querying for descendants of BaseAttribute on property
                        // level to get all different types of properties
                        // that are used to describe properties. As most properties
                        // contain the Lazy property we will use
                        // reflection when looking up the method info
                        // to be invoked and also try-catching the whole thing as
                        // performance is not of major importance in a prescanning phase.
                        BaseAttribute[] attributes = (BaseAttribute[])
                           property.GetCustomAttributes(typeof(BaseAttribute), false);
                        foreach (BaseAttribute attribute in attributes)
                        {
                            PropertyInfo attributePropertyInfo = 
                                     attribute.GetType().GetProperty("Lazy");
                            if (attributePropertyInfo == null) continue;
                            object lazySetting = 
                              attributePropertyInfo.GetGetMethod().Invoke(
                              attribute, new object[0]);

                            if (lazySetting is bool && (bool)lazySetting)
                                isLazy = AddLazyPropertyToList(property, propertyInfos);
                            if (lazySetting is Laziness && 
                                     ((Laziness)lazySetting) == Laziness.Proxy)
                                isLazy = AddLazyPropertyToList(property, propertyInfos);
                            if (lazySetting is RestrictedLaziness &&

                                   ((RestrictedLaziness)lazySetting) == 
                                                RestrictedLaziness.Proxy)
                                isLazy = AddLazyPropertyToList(property, propertyInfos);

                            // skip iterating through attributes and go to next property
                            // if a lazy specification has been found.
                            if (isLazy) break;
                        }
                    }
                }
                if (propertyInfos.Count > 0)
                    propertiesToInitialise.Add(type, propertyInfos);
            }
        }

        private static bool AddLazyPropertyToList(PropertyInfo property, 
                            List<PropertyInfo> propertyInfos)
        {
            propertyInfos.Add(property);
            return true;
        }

        /// <summary>
        /// Adds the proxy types to the proxyTypes IList.
        /// </summary>
        /// <param name="type">The proxy type.</param>

        /// <param name="classAttributes">The type class attributes.</param>
        /// <param name="joinedSubclassAttribute">The type
        /// joined-subclass attributes</param>
        private static void AddProxies(Type type, object[] classAttributes, 
                            object[] joinedSubclassAttribute)
        {
            if (classAttributes.Length > 0)
            {
                if (((ClassAttribute)classAttributes[0]).Proxy != null)
                    proxyTypes.Add(type);
            }
            else if (((JoinedSubclassAttribute)joinedSubclassAttribute[0]).Proxy != null)
                proxyTypes.Add(type);
        }

        /// <summary>

        /// Initialises the class by calling the static ctor.
        /// </summary>
        public static void StaticInitialiser()
        {
            //Call static ctor.
        }
        
        #endregion
    }
}

Points of Interest

One of the things I learned from this experience is to get to know the Configuration class a bit better. I would definitely recommend those of you working with NHibernate to have at least a quick look at it; you just might find some hacks that could be highly useful for you, depending on what you do with your ORM.

Conclusion

The class I brought here is far from being the actual way to reduce StaleObjectStateException damage. The way to reduce the damage would be by cutting the length of the session life. When this happens though, the session would not be open when accessing lazy properties; therefore, we use the LazyInitializer class.

Feel free to add any thoughts, criticisms, ideas, or questions.

History

  • 21 February, 2009: Initial post.
  • 23 February, 2009: Small additions made to the article.
  • 02 March, 2009: Fixed the AlternativeConstructor() method.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here