Development With A Dot

Blog on development in general, and specifically on .NET

Sponsors

News

My Friends

My Links

Permanent Posts

Portuguese Communities

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

Comments

René said:

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.

# April 17, 2011 6:32 PM

Ricardo Peres said:

@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é?

# April 18, 2011 3:48 AM

René said:

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é

# April 19, 2011 5:54 PM

Ricardo Peres said:

@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.

# April 20, 2011 3:27 AM

Brian said:

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.

# December 20, 2011 11:49 AM

Ricardo Peres said:

Brian,

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

RP

# December 23, 2011 12:46 PM

Michal said:

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?

# January 25, 2012 7:02 PM