Development With A Dot

Blog on development in general, and specifically on .NET

Sponsors

News

My Friends

My Links

Permanent Posts

Portuguese Communities

Implementing an Interceptor Using NHibernate’s Built In Dynamic Proxy Generator

Updated: important correction, reported by Christoph; when the event is raised, the first argument should be info.Target, not this.Proxy. Thanks!

NHibernate 3.2 came with an included proxy generator, which means there is no longer the need – or the possibility, for that matter – to choose Castle DynamicProxy, LinFu or Spring. This is actually a good thing, because it means one less assembly to deploy. Apparently, this generator was based, at least partially, on LinFu.

As there are not many tutorials out there demonstrating it’s usage, here’s one, for demonstrating one of the most requested features: implementing INotifyPropertyChanged. This interceptor, of course, will still feature all of NHibernate’s functionalities that you are used to, such as lazy loading, and such.

We will start by implementing an NHibernate interceptor, by inheriting from the base class NHibernate.EmptyInterceptor. This class does not do anything by itself, but it allows us to plug in behavior by overriding some of its methods, in this case, Instantiate:

   1: public class NotifyPropertyChangedInterceptor : EmptyInterceptor
   2: {
   3:     private ISession session = null;
   4:  
   5:     private static readonly ProxyFactory factory = new ProxyFactory();
   6:  
   7:     public override void SetSession(ISession session)
   8:     {
   9:         this.session = session;
  10:         base.SetSession(session);
  11:     }
  12:  
  13:     public override Object Instantiate(String clazz, EntityMode entityMode, Object id)
  14:     {
  15:         Type entityType = Type.GetType(clazz);
  16:         IProxy proxy = factory.CreateProxy(entityType, new _NotifyPropertyChangedInterceptor(), typeof(INotifyPropertyChanged)) as IProxy;
  17:         
  18:         _NotifyPropertyChangedInterceptor interceptor = proxy.Interceptor as _NotifyPropertyChangedInterceptor;
  19:         interceptor.Proxy = this.session.SessionFactory.GetClassMetadata(entityType).Instantiate(id, entityMode);
  20:  
  21:         this.session.SessionFactory.GetClassMetadata(entityType).SetIdentifier(proxy, id, entityMode);
  22:  
  23:         return (proxy);
  24:     }
  25: }

Then we need a class that implements the NHibernate dynamic proxy behavior, let’s place it inside our interceptor, because it will only need to be used there:

   1: class _NotifyPropertyChangedInterceptor : NHibernate.Proxy.DynamicProxy.IInterceptor
   2: {
   3:     private PropertyChangedEventHandler changed = delegate { };
   4:  
   5:     public Object Proxy
   6:     {
   7:         get;
   8:         set;}
   9:  
  10:     #region IInterceptor Members
  11:  
  12:     public Object Intercept(InvocationInfo info)
  13:     {
  14:         Boolean isSetter = info.TargetMethod.Name.StartsWith("set_") == true;
  15:         Object result = null;
  16:  
  17:         if (info.TargetMethod.Name == "add_PropertyChanged")
  18:         {
  19:             PropertyChangedEventHandler propertyChangedEventHandler = info.Arguments[0] as PropertyChangedEventHandler;
  20:             this.changed += propertyChangedEventHandler;
  21:         }
  22:         else if (info.TargetMethod.Name == "remove_PropertyChanged")
  23:         {
  24:             PropertyChangedEventHandler propertyChangedEventHandler = info.Arguments[0] as PropertyChangedEventHandler;
  25:             this.changed -= propertyChangedEventHandler;
  26:         }
  27:         else
  28:         {
  29:             result = info.TargetMethod.Invoke(this.Proxy, info.Arguments);
  30:         }
  31:  
  32:         if (isSetter == true)
  33:         {
  34:             String propertyName = info.TargetMethod.Name.Substring("set_".Length);
  35:             this.changed(this.Target, new PropertyChangedEventArgs(propertyName));
  36:         }
  37:  
  38:         return (result);
  39:     }
  40:  
  41:     #endregion
  42: }

What this does for every interceptable method (those who are either virtual or from the INotifyPropertyChanged) is:

  • For methods that came from the INotifyPropertyChanged interface, add_PropertyChanged and remove_PropertyChanged (yes, events are methods Winking smile), we add an implementation that adds or removes the event handlers to the delegate which we declared as changed;
  • For all the others, we direct them to the place where they are actually implemented, which is the Proxy field;
  • If the call is setting a property, it fires afterwards the PropertyChanged event.

In order to use this, we need to add the interceptor to the Configuration before building the ISessionFactory:

   1: using (ISessionFactory factory = cfg.SetInterceptor(new NotifyPropertyChangedInterceptor()).BuildSessionFactory())
   2: {
   3:     using (ISession session = factory.OpenSession())
   4:     using (ITransaction tx = session.BeginTransaction())
   5:     {
   6:         Customer customer = session.Get<Customer>(100);    //some id
   7:         INotifyPropertyChanged inpc = customer as INotifyPropertyChanged;
   8:         inpc.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e)
   9:         {
  10:             //fired when a property changes
  11:         };
  12:         customer.Address = "some other address";    //will raise PropertyChanged
  13:         customer.RecentOrders.ToList();            //will trigger the lazy loading
  14:     }
  15: }

Any problems, questions, do drop me a line!

Comments

Vijay said:

Thanks for your solution, I will try it when I go home. One question though - how do you intend to generate proxies for mapped objects instantiated via code like following?

Customer customer = new Customer();

where Customer is a class mapped to table.

Vij

# June 25, 2012 6:49 AM

Ricardo Peres said:

Vijay:

That is not possible and doesn't ever happen with today's NHibernate.

# June 25, 2012 7:49 AM

Vijay said:

Sorry I should have phrased ny question differently. I know it is not possible to 'proxyfy' the instances created via 'new' operator. ow would you get proxies say in factory pattern?

Customer customer  = SomeFactory.Create<Customer>();

where Create method would return proxies which have same behaviour as the proxies returned by NH using your code.

Hope that makes some sense.

Vij

# June 25, 2012 8:28 AM

Ricardo Peres said:

Vijay:

For generating "my" proxies, it is very easy:

ProxyFactory factory = new ProxyFactory();

IProxy proxy = factory.CreateProxy(entityType, new _NotifyPropertyChangedInterceptor(), typeof(INotifyPropertyChanged)) as IProxy;

For generating NHibernate's:

IProxy proxy = this.session.SessionFactory.GetClassMetadata(entityType).SetIdentifier(proxy, id, entityMode);

My solution combines both. Drop me an email if you want to discuss this even further.

# June 25, 2012 9:11 AM

Christoph said:

Hi Ricardo,

we tried your solution and everyhing works fine, NotifyPropertyChanged message has been thrown but we get the strange effect that WPF UI doesn't update.

It seems that the interface is implemented in proxy but WPF does not care.

Checked all bindings and when implementing INPC by hand everything works fine, when implementing via proxy this strange effect.

Best regards

Christoph

# July 3, 2012 9:35 AM

Ricardo Peres said:

Christoph:

Thanks for the feedback!

That is strange... have you looked at the PropertyName property or the sender in the PropertyChanged event?

# July 3, 2012 10:00 AM

Christoph said:

It seems that this line of your code is wrong:

this.changed(this.Proxy, new PropertyChangedEventArgs(propertyName));

Notification should be called on proxy, not on poco:

this.changed(info.Target, new PropertyChangedEventArgs(propertyName));

After correcting this everything works fine.

# July 3, 2012 10:55 AM

Ricardo Peres said:

Christoph:

Yes, I was thinking the same... thanks for your feedback, I'll update the code.

# July 3, 2012 2:13 PM

Christoph said:

I'm afraid this was the wrong line you updated. Should be line 35.

# July 4, 2012 3:49 AM

Ricardo Peres said:

Christoph:

That's what happens when fixing things on vacation, without actually trying... thanks! ;-)

# July 4, 2012 4:01 AM

lester said:

hello  Ricardo Peres:

For generating "my" proxies, it is very easy:

ProxyFactory factory = new ProxyFactory();

IProxy proxy = factory.CreateProxy(entityType, new _NotifyPropertyChangedInterceptor(), typeof(INotifyPropertyChanged)) as IProxy;

For generating NHibernate's:

IProxy proxy = this.session.SessionFactory.GetClassMetadata(entityType).SetIdentifier(proxy, id, entityMode);

My solution combines both

i still don't konw how to do with next code...

# September 11, 2012 11:45 PM

mario said:

ProxyFactory factory = new ProxyFactory();

IProxy proxy = factory.CreateProxy(typeof(Customer), new _NotifyPropertyChangedInterceptor(), typeof(INotifyPropertyChanged)) as IProxy;

var l = (proxy as Customer).Id;

this code return: Non-static method requires a target.

what I wrong?

thanks

# September 17, 2012 2:47 PM

Scooletz said:

What about calling property changed events by NHibernate? One should change the mapping of properties to use backing fields accessors. Then the saving and changing state would not raise the events, which may be desired in cases like auditing (adding created/modified date).

# December 27, 2012 9:59 AM

Ricardo Peres said:

Scooletz:

I never use backing fields. I don't understand what you mean.

# February 2, 2013 4:56 AM