How to use NHibernate Lazy Initializing Proxies with Web Services or WCF

By | August 5, 2009

Working with object trees is risky business when combined with remoting. Object trees often have very deep branches and circular references that will clog your pipes and overflow your stack when not handled properly.

NHibernate has addressed this same problem in the object-relational space with a very cool feature called lazy initialization, implemented using the Castle DynamicProxy library. By default, when an entity is loaded, its related entities are populated with proxies. The use of a proxy allows the entity to remain uninitialized until the moment one of its non-identifying properties is accessed, which in many cases is never – thus minimizing database queries. This strategy also eradicates the problem with loading an entire object tree from the database when only a single entity is needed.

Proxies become a problem when combined with Web services or WCF. Serialization of objects to XML requires knowledge of the data type to be serialized (accomplished with the KnownType attribute in WCF). The dynamically generated nHibernate proxies are not known to the serializer and thus a request to the service results in an exception such as System.Net.WebException: The underlying connection was closed: The connection was closed unexpectedly.

In order to make these entities safe for sending through the pipes, we must “unproxy” them and impose a limit to the object tree depth. The following utility class can be used for doing both by simply calling possiblyProxiedEntity.UnproxyObjectTree(mySessionFactory, maxDepth):

using System;
using NHibernate;
using NHibernate.Metadata;
using NHibernate.Proxy;
 
namespace Trentacular.Data.NHibernate.Util
{
    public static class NHibernateProxyUtils
    {
        /// <summary>
        /// Force initialization of a proxy or persistent collection.
        /// </summary>
        /// <param name="persistentObject">a persistable object, proxy, persistent collection or null</param>
        /// <exception cref="HibernateException">if we can't initialize the proxy at this time, eg. the Session was closed</exception>
        public static T Unproxy<T>(this T persistentObject)
        {
            var proxy = persistentObject as INHibernateProxy;
 
            if (proxy != null)
                return (T)proxy.HibernateLazyInitializer.GetImplementation();
 
            return persistentObject;
        }
 
        /// <summary>
        /// Gets the underlying class type of a persistent object that may be proxied
        /// </summary>
        public static Type GetUnproxiedType<T>(this T persistentObject)
        {
            var proxy = persistentObject as INHibernateProxy;
            if (proxy != null)
                return proxy.HibernateLazyInitializer.PersistentClass;
 
            return persistentObject.GetType();
        }
 
        /// <summary>
        /// Force initialzation of a possibly proxied object tree up to the maxDepth.
        /// Once the maxDepth is reached, entity properties will be replaced with
        /// placeholder objects having only the identifier property populated.
        /// </summary>
        public static T UnproxyObjectTree<T>(this T persistentObject, ISessionFactory sessionFactory, int maxDepth)
        {
            // Determine persistent type of the object
            var persistentType = persistentObject.GetUnproxiedType();
 
            var classMetadata = sessionFactory.GetClassMetadata(persistentType);
 
            // If we've already reached the max depth, we will return a placeholder object
            if (maxDepth < 0)
                return CreatePlaceholder(persistentObject, persistentType, classMetadata);
 
            // Now lets go ahead and make sure everything is unproxied
            var unproxiedObject = persistentObject.Unproxy();
 
            // Iterate through each property and unproxy entity types
            for (int i = 0; i < classMetadata.PropertyTypes.Length; i++)
            {
                var nhType = classMetadata.PropertyTypes[i];
                var propertyName = classMetadata.PropertyNames[i];
                var propertyInfo = persistentType.GetProperty(propertyName);
 
                // Unproxy of collections is not currently supported.  We set the collection property to null.
                if (nhType.IsCollectionType)
                {
                    propertyInfo.SetValue(unproxiedObject, null, null);
                    continue;
                }
 
                if (nhType.IsEntityType)
                {
                    var propertyValue = propertyInfo.GetValue(unproxiedObject, null);
 
                    if (propertyValue == null)
                        continue;
 
                    propertyInfo.SetValue(
                        unproxiedObject,
                        propertyValue.UnproxyObjectTree(sessionFactory, maxDepth - 1),
                        null
                        );
                }
            }
 
            return unproxiedObject;
        }
 
        /// <summary>
        /// Return an empty placeholder object with the Identifier set.  We can safely access the identifier
        /// property without the object being initialized.
        /// </summary>
        private static T CreatePlaceholder<T>(T persistentObject, Type persistentType, IClassMetadata classMetadata)
        {
            var placeholderObject = (T)Activator.CreateInstance(persistentType);
 
            if (classMetadata.HasIdentifierProperty)
            {
                var identifier = classMetadata.GetIdentifier(persistentObject, EntityMode.Poco);
                classMetadata.SetIdentifier(placeholderObject, identifier, EntityMode.Poco);
            }
 
            return placeholderObject;
        }
    }
}

There are several approaches on where and when this should take place depending on if you are using ASP.Net 2.0 Web services or WCF which I am not going to go into in this post. If you are working with WCF, Tim Scott has written about a good approach here.

16 thoughts on “How to use NHibernate Lazy Initializing Proxies with Web Services or WCF

  1. Mark

    This is really useful. I would like to use this piece of code in my project. What is the license for this?

  2. Trent Post author

    @Mark – Any code posted on this blog is released to the public domain, which means have at it. If you wish to credit me, feel free to mutter my name under your breath.

  3. Kurt

    If you have cascading updates will NHiberante’s dirty checking pickup that you have replaced the proxy with a new instance containing only the id and try and save the placeholder as an updated version of the object?

  4. Trent Post author

    @Kurt

    You are absolutely right. It sounds like you are not only sending your business model objects to your clients, but also receiving them to be updated as an argument to one of your service methods.

    I would highly recommend against doing this for updates. Instead, think about using a “changed” properties dictionary to pass the values you need updated, then in your service, load up a fresh “proxy” object, set the changed properties, and then update. This also will help eliminate problems related to concurrent modifications.

  5. Kurt

    I’m actually not using WCF, just trying to copy an entity in its current state (dirty, not yet committed) to a work queue on a background thread. The background thread does not update the entity at all, only reads data from it, if the main thread fails to commit the transaction a compensating action will be performed.

    The problem I had was the entity was not fully loaded and the background thread would attempt to read properties that were lazy loaded. Although our session is thread local the the NHibernate proxies have their own reference to the session object on the main thread so as long as the main thread had not yet closed the session this would actually work. However, more often than not (thankfully) it did not.

    I could create a DTO object and copy all the relevant properties into it rather than sending the domain objects, but that seems like a lot of extra work for little benefit when the domain objects already have what I need.

    I was trying to be lazy and use serialization but wanted a deep copy and I was kinda thinking your code did what I wanted but was kinda expecting it would copy the object before modifying it. Thus the issue with dirty checking, but I guess if you don’t plan on committing the transaction this isn’t an issue.

    I still managed to be somewhat lazy and just initialized the properties on original object without putting in the place holders. Then used binary serialization to clone the object which nulls the session preventing lazy loading on the background thread from inadvertently using the session on main thread. Leaving the proxies there actually works a bit better for me since the uninitialized proxies will throw an exception now if the thread tries to access an uninitialized property where as the placeholder would just return the initial ctor value, but probably not so well for wcf data contract serialization.

  6. Ali

    What if we are sending a soap request, do you think we still need to unproxy the object?
    I did ran a small test and it is sending proxied data correclty….

  7. Tom Blench

    Thanks – this helped me out as I’m also working with WCF.

    Since my service works with collections I made a tweak which works for my purposes:

    // Unproxy of collections is supported. Iterate over the collection and unproxy in turn.
    if (nhType.IsCollectionType)
    {
    var propertyValue = propertyInfo.GetValue(unproxiedObject, null);
    System.Collections.ICollection c = propertyValue as System.Collections.ICollection;
    foreach (var item in c)
    {
    if (propertyValue == null)
    continue;

    item.UnproxyObjectTree(sessionFactory, maxDepth – 1);
    }
    continue;
    }

  8. Delanojr

    Hi,
    Thanks for the code. How do you use this code.Please could you show a little example on how to consume it.

  9. Rob

    Great solution! Helped me to solve an issue with proxied objects. Thanks for sharing this great knowledge.

  10. Petey

    This is awesome!

    Only problem I’ve found it that we have circular references in our data (particular using vertices/nodes graphs) which sends the code into a loop.

    Anyone done any work with this? I guess the MaxLevel bit is intended to prevent this.

    Cheers

  11. Paul D

    This is great. I know I should be using DTO and that, but sometimes it’s just not practical. I agree the should always be uesd for submitting data, but for returning data from the db it’s not so black and white. This is an excellent tool for returning entities (which will only be used for accessing their data). Also Petey, I think what you need for circular refrences is [DataContract(IsReference = true)]

  12. joylogic

    // Unproxy of collections is supported. Iterate over the collection and unproxy in turn.
    if (nhType.IsCollectionType)
    {

    var propertyValue = propertyInfo.GetValue(unproxiedObject, null);
    System.Collections.ICollection c = propertyValue as System.Collections.ICollection;
    Type[] typeArgs = c.GetType().GetGenericArguments();
    Type algTypeGen = Assembly.Load(“mscorlib”).GetType(“System.Collections.Generic.List`1″);
    Type algType= algTypeGen.MakeGenericType(typeArgs);

    var l = Activator.CreateInstance(algType);

    IList lst = l as IList;
    foreach (var item in c)
    {
    if (propertyValue == null)
    continue;

    item.UnproxyObjectTree(sessionFactory, maxDepth – 1);
    lst.Add(item);
    }
    propertyInfo.SetValue(unproxiedObject, lst, null);
    continue;

    }

Leave a Reply

Your email address will not be published. Required fields are marked *