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!

                             

20 Comments

  • 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

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

  • 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();

    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

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

  • 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



  • Christoph:
    Thanks for the feedback!
    That is strange... have you looked at the PropertyName property or the sender in the PropertyChanged event?

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

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

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

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

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

  • 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

  • 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).

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

  • Hi,

    As mario and lester, I too have the same problem. I don't knoe much about proxys and I am trying to creante a new Customer entity, but I don't know how.

    Can you please show me the correct code.

    ProxyFactory factory = new ProxyFactory();
    Customer cust = factory.CreateProxy(typeof(Customer), new _NotifyPropertyChangedInterceptor(), typeof(INotifyPropertyChanged)) as Customer;

    cust.Name = "test"; // exception


    TIA

  • Hi, Matjaz!
    You can get the code from https://bitbucket.org/syncfusiontech/nhibernate-succinctly/.

  • Hello,

    I am very grateful for your blog, your help and link to your project, from which I will learn a lot. I do not want to be disrespectful, but in your project I haven't found any code where you would create any ordinary object that supports INotifyPropertyChanged interface.

    With my limited knowledge and your help I have written this code fragment that may be useful to someone else.

    Customer Customer = new Customer();
    Assert.IsFalse(Customer is INotifyPropertyChanged);

    ProxyFactory factory = new ProxyFactory();
    IProxy proxy = factory.CreateProxy(typeof(Customer), new _NotifyPropertyChangedInterceptor(Customer), typeof(INotifyPropertyChanged)) as IProxy;
    Customer pCustomer = (Customer)proxy;
    Assert.IsTrue(pCustomer is INotifyPropertyChanged);

  • Matjaz:
    The point is to create any ordinary object that DOES NOT support INotifyPropertyChanged!
    In https://bitbucket.org/syncfusiontech/nhibernate-succinctly/src/7d9b91112e9e86f2025f6946145b79f9a9cacaa0/Console/Program.cs?at=master, you can find an example of using the interception, see Interceptor method.

  • Hm,

    Ordinary object that does not implement in interface is easy to create (new Customer()). But the problem is if I use this object in ViewModel. XAML expect object that implement change interface.

    Let us take some detail customer view. If I got a customer form DB via NHibernate (like Get<Customer>(103)) everything works as expected because this 'customer' object implement required interface.

    But if user want to create a new customer record I will show him the same XAML View but with empty record (like new Customer(), or from factory or whatever...). The point here is that this is POCO and does not implement notify interface so XAML does not work.

    I (and obviously others) were searching for a code that would "proxify" POCO object with your Interceptor so any POCO object would be equally useful in XAML as object return from NHibernate Get methods.

    PS: I spotted a 'typo' in my previous post 'Customer' in _NotifyPropertyChangedInterceptor(Customer) should be object not type; so the correct code is _NotifyPropertyChangedInterceptor(customer) 'small c'.

  • Ahh, I see what you mean... try this:

    public sealed class NotifyPropertyChangedInterceptor : EmptyInterceptor
    {

    //everything else...
    public static T CreateProxy<T>() where T : class, new()
    {
    return (T)factory.CreateProxy(typeof(T), new _NotifyPropertyChangedInterceptor(new T()), typeof(INotifyPropertyChanged));
    }
    }

    And use it as:

    var proxy = NotifyPropertyChangedInterceptor.CreateProxy<Customer>();
    var inpc = proxy as INotifyPropertyChanged;

Add a Comment

As it will appear on the website

Not displayed

Your website