Advance: Extending NHibernate Proxies

time to read 44 min | 8606 words

There was some interest in extending the way NHibernate deals with the entities, and I have just commited those changes into the trunk. Notice that since NHibernate 1.2.0 is at feature freeze right now, this will not be in 1.2.0 RTM, but at a later version.

At any rate, let us see how we can extend NHibernate so it would offer automatic implementation for INotifyPropertyChanged. Some caveats: It works only for entities that have lazy loading enabled (the default for 1.2.0) which were retreived from NHibernate.

You can see the full test here, but let me go through the implementation. First here is the test case:

[Test]

public void CanImplementNotifyPropertyChanged()

{

       using (ISession s = OpenSession())

       {

              Blog blog = new Blog("blah");

              Assert.IsFalse(blog is INotifyPropertyChanged);

              s.Save(blog);

              s.Flush();

       }

 

       using (ISession s = OpenSession())

       {

              Blog blog = (Blog)s.Load(typeof(Blog), 1);

              INotifyPropertyChanged propertyChanged = (INotifyPropertyChanged)blog;

              string propChanged = null;

              propertyChanged.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)

              {

                     propChanged = e.PropertyName;

              };

 

              blog.BlogName = "foo";

              Assert.AreEqual("BlogName", propChanged);

       }

}

As you can see, when we create the entity manually, it does not implements INotifyPropertyChanged.  But when we load the object from NHibernate, not only does it implements INotifyProperyChanged, but it behaves correctly as well. They key is in extending the Proxy Factory that NHibernate uses for lazy loading.

We need to specify the proxy factory type that we would like to use:

protected override void BuildSessionFactory()

{

       cfg.SetProxyFactoryClass(typeof(DataBindingProxyFactory));

       base.BuildSessionFactory();

}

And now, let us see how the DataBindingProxyFactory works:

public class DataBindingProxyFactory : CastleProxyFactory

{

       public override INHibernateProxy GetProxy(object id, ISessionImplementor session)

       {

              try

              {

                     CastleLazyInitializer initializer = new DataBindingInterceptor(_persistentClass, id,

                                _getIdentifierMethod, _setIdentifierMethod, session);

 

                     object generatedProxy = null;

 

                     ArrayList list = new ArrayList(_interfaces);

                     list.Add(typeof(INotifyPropertyChanged));

                     System.Type[] interfaces = (System.Type[])list.ToArray(typeof(System.Type));

                     if (IsClassProxy)

                     {

                           generatedProxy = _proxyGenerator.CreateClassProxy(_persistentClass, interfaces, initializer, false);

                     }

                     else

                     {

                           generatedProxy = _proxyGenerator.CreateProxy(interfaces, initializer, new object());

                     }

 

                     initializer._constructed = true;

                     return (INHibernateProxy)generatedProxy;

              }

              catch (Exception e)

              {

                     log.Error("Creating a proxy instance failed", e);

                     throw new HibernateException("Creating a proxy instance failed", e);

              }

       }

}

Here we did two things, We specify a different interceptor, which inherits from the default interceptor, that handles lazy loading, and we specify that the proxy that NHibernate returns should also implement INotifyPropertyChanged. Now, we need to see how the DataBindingInteceptor works:

public class DataBindingInterceptor : CastleLazyInitializer

{

       private PropertyChangedEventHandler subscribers = delegate { };

 

       public DataBindingInterceptor(System.Type persistentClass, object id, 
               
MethodInfo getIdentifierMethod, MethodInfo setIdentifierMethod, ISessionImplementor session)

              : base(persistentClass, id, getIdentifierMethod, setIdentifierMethod, session)

       {

       }

 

       public override object Intercept(IInvocation invocation, params object[] args)

       {

              if (invocation.Method.DeclaringType == typeof(INotifyPropertyChanged))

              {

                     PropertyChangedEventHandler propertyChangedEventHandler = (PropertyChangedEventHandler)args[0];

                     if (invocation.Method.Name.StartsWith("add_"))

                     {

                           subscribers += propertyChangedEventHandler;

                     }

                     else

                     {

                           subscribers -= propertyChangedEventHandler;

                     }

                     return null;

              }

              object result = base.Intercept(invocation, args);

              if (invocation.Method.Name.StartsWith("set_"))

              {

                     subscribers(this, new PropertyChangedEventArgs(invocation.Method.Name.Substring(4)));

              }

              return result;

       }

}

We extend CastleLazyInitializer, which handles the usual lazy loading related logic for NHibernate, and when we get a method, we check to see if it a call to a method from the INotifyPRopertyChanged interface. If it is, we handle the add/remove of the event subscriber. But that is not the interesting part.

The interesting part is that after we let the method be processed normally (which is what the call to base.Intercept() does, we check to see if this is a property setter, and raise the appropriate event if it does.

The client code for this is very natural, and it took about 70 lines of code to add this functionality.