NHibernate Interceptor for Dynamic Proxy Generation

NHibernate comes along with a nice code generation API that is used, for example, for lazy-loading proxy generation. It can, however, be used for other purposes, such as adding interfaces to the loaded entities, for example, INotifyPropertyChanged coming to my mind. Ayende and José have talked about this in the past, but I decided to publish my own updated version.

It has two parts: a custom NHibernate interceptor class that can be added to Configuration or session creation and a custom Castle DynamicProxy interceptor that is created from the former for each new entity loaded from the DB.

Here's the skeleton. You just have to fill in the blanks to add your own behavior.


public class CustomInterceptor : NHibernate.EmptyInterceptor
{
	class _CustomInterceptor : Castle.DynamicProxy.IInterceptor
	{
		private Boolean finishedLoading = false;

		void Castle.DynamicProxy.IInterceptor.Intercept(IInvocation invocation)
		{
			Object target = (invocation.InvocationTarget == null) ? invocation.Proxy : invocation.InvocationTarget;

			//check if the entity has finished loading
			if ((invocation.InvocationTarget == null) && (this.finishedLoading == false))
			{
				this.finishedLoading = true;
			}

			//TODO: do something before base method call
			if (invocation.Method.Name.StartsWith("get_") == true)
			{
				//getter invocation
			}
			else if (invocation.Method.Name.StartsWith("set_") == true)
			{
				//setter invocation
			}
			else
			{
				//method invocation
			}

			if (invocation.InvocationTarget != null)
			{
				//proceed with base implementation (base getter, setter or method call)
				invocation.Proceed();
			}

			//TODO: do something after base method call
			if (invocation.Method.Name.StartsWith("get_") == true)
			{
				//getter invocation
			}
			else if (invocation.Method.Name.StartsWith("set_") == true)
			{
				//setter invocation
			}
			else
			{
				//method invocation
			}
		}
	}

	private static readonly ProxyGenerator proxyGenerator = new ProxyGenerator();
	private ISession session = null;

	public static Object CreateProxy(Type type)
	{
		List<Type> interfaces = new List<Type>();
		//TODO: add interfaces to list

		Object instance = null;

		if ((interfaces.Count != 0) && (type.IsSealed == false))
		{
			//TODO: pass any custom parameters to the _CustomInterceptor class
			instance = proxyGenerator.CreateClassProxy(type, interfaces.ToArray(), new _CustomInterceptor());
		}
		else
		{
			instance = Activator.CreateInstance(type);
		}

		return(instance);
	}

	public static T CreateProxy<T>() where T: class, new()
	{
		Type type = typeof(T);
		return(CreateProxy(type) as T);
	}

	public override String GetEntityName(Object entity)
	{
		if (entity.GetType().Assembly.FullName.StartsWith("DynamicProxyGenAssembly2") == true)
		{
			return (entity.GetType().BaseType.FullName);
		}
		else
		{
			return (entity.GetType().FullName);
		}
	}

	public override void SetSession(ISession session)
	{
		this.session = session;
		base.SetSession(session);
	}

	public override Object Instantiate(String clazz, EntityMode entityMode, Object id)
	{
		if (entityMode == EntityMode.Poco)
		{
			Type type = Type.GetType(clazz, false);

			if (type != null)
			{
				Object instance = CreateProxy(type);

				this.session.SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance, id, entityMode);

				return (instance);
			}
		}

		return (base.Instantiate(clazz, entityMode, id));
	}
}

You can add the interceptor at two levels:

  • The Configuration object: all sessions will inherit this interceptor
  • The ISession object

Here's an example for each:


Configuration cfg = ...;
cfg.SetInterceptor(new CustomInterceptor());

ISessionFactory factory = cfg.BuildSessionFactory();
ISession session = factory.OpenSession(new CustomInterceptor());

As you can see, the outer _CustomInterceptor class is the one that NHibernate must know. You should add any interfaces that the proxy should implement. The inner _CustomInterceptor class becomes part of the generated proxy, and the Intercept method will be called for each interceptable (virtual or new) property and method. If you want, you can add code before and after the base call.

I have my own implementation which adds some common interfaces used for data binding, editing and validation: example, INotifyPropertyChanged and example, INotifyPropertyChanging, example, IEditableObject and example, IDataErrorInfo (with the help of NHibernate Validator). Feel free to roll your own!

Bookmark and Share

                             

7 Comments

  • Hi,

    your
    public static Object CreateProxy(Type type)
    method has no Datatype T and the static method can not use "this" in line 065. Can you please fix it?

    A example implementation where also good where the placeholder are filled with such as INotifyPropertyChanged.

    Thanks.

  • @René:
    Sorry, it is fixed.
    I may be posting a fully functional example, but, since it isn't hard, why don't you try it for yourself, starting with the samples from Ayende and José?

  • The Datatype T haven't you fixed. It's still unknown in Line 61, 66, 70.

    About the fully functional example:
    People search after complete articles where they must not try&error ;)
    If I must read other article to understand your article then it's incomplete. And for you it's done in 10 Minutes while many many other readers must invest everyone houres...

    Thanks & Best regards
    René

  • @René,
    It is (hopefully) fixed now.
    As for the full example, you can see Ayende and José's examples and try to replicate it with my code, it is very easy.

  • Hi, would you please post your implementation with INotifyPropertyChanged and INotifyPropertyChanging?


    I'm having difficulty adding logic that checks whether the previous value is different from the current value, and only raising the propertychanged event if they are different.

  • Brian,
    In my code, if the property is set, I fire the event handler; no checking at all.
    RP

  • Hi

    Did someone try to use it for a lazy-loaded object graph?

    To me it just works just for leafs (the leafs really contain an instance of _CustomInterceptor). Other objects higher in the hiearchy that need to do some lazy loading themselves as contain references to other lazy-loaded objects are being intercepted by NHibernate.Proxy.DefaultLazyInitializer

    any ideas?

Comments have been disabled for this content.