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
private static IDictionary<Type, List<PropertyInfo>>
propertiesToInitialise =
new Dictionary<Type, List<PropertyInfo>>();
private static IList<Type> proxyTypes = new List<Type>();
#endregion
public static T ImmediateLoad<T>(int id, ISession session)
{
T entity = session.Load<T>(id);
return InitializeCompletely(entity, session);
}
public static T InitializeCompletely<T>(T entity, ISession session)
{
ExtractNHMappedProperties(entity, 0, 0, true, session);
return entity;
}
public static T InitializeEntity<T>(T entity,
int maxFetchDepth, ISession session)
{
if (maxFetchDepth < 0 || maxFetchDepth > 20) maxFetchDepth = 20;
ExtractNHMappedProperties(entity, 0, maxFetchDepth, false, session);
return entity;
}
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)
{
if (search)
{
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;
}
}
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);
}
}
}
}
}
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
session.Lock(owner, LockMode.None);
NHibernateUtil.Initialize(proxy);
}
break;
}
}
}
}
#region ctor
public static void AlternativeConstructor()
{
var cfg = new Configuration();
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;
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;
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());
}
}
static LazyInitializer()
{
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;
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);
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;
}
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);
}
public static void StaticInitialiser()
{
}
#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.