Ioannis Panagopoulos blog

Tutorials on HTML5, Javascript, WinRT and .NET

NHibernate, PropertyChanged event and WPF

by Ioannis Panagopoulos

A typical implementation of an entity that supports the INotifyPropertyChanged interface for WPF Binding is for example as follows:

public class Detail:INotifyPropertyChanged
{

    private long _iD;
    public virtual long ID
    {
        get { return _iD; }
        set { _iD = value;
              if (PropertyChanged != null)
                  PropertyChanged(this, new PropertyChangedEventArgs("ID"));}
    }
 
    private string _line1;
    public virtual string Line1
    {
        get { return _line1; }
        set { _line1 = value;
              if (PropertyChanged != null)
                  PropertyChanged(this, new PropertyChangedEventArgs("Line1"));}
    }
 
    private string _line2;
    public virtual string Line2
    {
        get { return _line2; }
        set { _line2 = value;
              if (PropertyChanged != null)
                  PropertyChanged(this, new PropertyChangedEventArgs("Line2"));}
    }
 
    private Entity _relatedEntity;
    public virtual Entity RelatedEntity
    {
        get { return _relatedEntity; }
        set { _relatedEntity = value;
              if (PropertyChanged != null)
                  PropertyChanged(this, new PropertyChangedEventArgs("RelatedEntity"));}
    }
    public virtual event PropertyChangedEventHandler PropertyChanged;

 

Class Detail supports the INotfiyPropertyChanged interface and is ready for data-binding in a WPF Window. Try to bind the properties "Line1" and "Line2" to TextBoxes in a WPF Window and soon you will realize that if you load the entity from a database using NHibernate by:

 

  • Load<Detaill>(id) : The PropertyChanged events are ignored by WPF
  • Get<Detail>(id): The PropertyChanged events are processed as expected by WPF

This behaviour is due to the fact that Load returns a proxy object while Get doesn't. More seriously though if in the same window you want to bind to properties of the property RelatedEntity (eg RelatedEntity.Description)  which is instantiated by a many-to-one relationship and you use "Lazy-Loading", you will have a proxied Entity object and the PropertyChanged event will be ignored as before.

It seems that when a proxy is involved, ProperyChanged events are ignored by WPF.The obvious choice is to always use Get and disable "Lazy-Loading" which is of course something that usually is not an option.

But there seems to be another solution:

The reason the problem occurs has to do with the fact that WPF expects a PropertyChanged event from the proxy object and gets it from the actual object. This can be better explained with the following figure:

 

When the property "Line1" is changed(Bottom), a PropertyChanged event is generated that goes through the proxy to the WPF TextBox. WPF ignores the event since it is databound to the _detailProxy object and not to the _detail object. In other words it awaits events of the form PropertyChanged (_detailProxy,"Line1") and not PropertyCahnged (_detail,'Line1").

The solution is to extend the proxy implementaion so that it takes the resoonsibility of notifying WPF when a property is changed. We will keep the original implementation of the entity as is to support the NotifyPropertyChanged mechanism when NHibernate is not involved in the object creation process and we will create a new Interceptor for the proxy to support the same functionality when NHibernate creates the object for us. Moreover the process will be as non invasive as possible in case in future implementations of NHibernate the issue is resolved.

We create a new file called NotifyProprtyChangedInterceptor.cs and first, we add a bunch of .using statements (make sure you reference the appropriate libraries):

using System.ComponentModel;
using NHibernate.Proxy.Poco.Castle;
using System.Reflection;
using NHibernate.Engine;
using NHibernate.Type;
using Castle.Core.Interceptor;
using Castle.DynamicProxy.Generators;
 
using System.Collections;
using NHibernate.Proxy;
using NHibernate;

We implement in it the following class which will create our extended proxy:

// We override this class implementation to handle the proxy creation ourselves. We
// need to specify our own Interceptor when a method of our entity is called.
public class DataBindingNotifyPropertyProxyFactory : CastleProxyFactory
{
    public override INHibernateProxy GetProxy(object id, ISessionImplementor session)
    {
        // If it is not a proxy for a class do what you usually did.
        if (!IsClassProxy) return base.GetProxy(id, session);
 
        try
        {               
            CastleLazyInitializer initializer = new DataBindingInterceptor(EntityName,PersistentClass, id,
                       GetIdentifierMethod, SetIdentifierMethod,ComponentIdType, session);
           
            // Add to the list of the interfaces that the proxy class will support the INotifyPropertyChanged interface.
            // This is only needed in the case when we need to cast our proxy object as INotifyPropertyChanged interface.
            ArrayList list = new ArrayList(Interfaces);
            list.Add(typeof(INotifyPropertyChanged));
            System.Type[] interfaces = (System.Type[])list.ToArray(typeof(System.Type));
 
            //We create the proxy (for the class, Interfaces Supported, Interceptor to use)
            object generatedProxy = DefaultProxyGenerator.CreateClassProxy(PersistentClass, interfaces, initializer);
 
            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);
        }
    }
}

In the same file we implement the interceptor that is used by the proxy:

public class DataBindingInterceptor : CastleLazyInitializer
 {
     private PropertyChangedEventHandler subscribers = delegate { };
 
     public DataBindingInterceptor(String EntityName,Type persistentClass, object id,MethodInfo getIdentifierMethod, MethodInfo setIdentifierMethod,IAbstractComponentType aType, ISessionImplementor session)
         : base(EntityName,persistentClass, id, getIdentifierMethod, setIdentifierMethod,aType, session)
     {
     }
 
     public override void Intercept (IInvocation invocation)
     {
         // WPF will call a method named add_PropertyChanged to subscribe itself to the property changed events of
         // the given entity. The method to call is stored in invocation.Arguments[0]. We get this and add it to the
         // proxy subscriber list.
         if (invocation.Method.Name.Contains("PropertyChanged"))
         {
             PropertyChangedEventHandler propertyChangedEventHandler = (PropertyChangedEventHandler)invocation.Arguments[0];
             if (invocation.Method.Name.StartsWith("add_"))
             {
                 subscribers += propertyChangedEventHandler;
             }
             else
             {
                 subscribers -= propertyChangedEventHandler;
             }
         }
        
         // Here we call the actual method of the entity
         base.Intercept(invocation);
 
         // If the method that was called was actually a proeprty setter (set_Line1 for example) we generate the
         // PropertyChanged event for the property but with event generator the proxy. This must do the trick.
         if (invocation.Method.Name.StartsWith("set_"))
         {              
             subscribers(invocation.InvocationTarget, new PropertyChangedEventArgs(invocation.Method.Name.Substring(4)));
         }
     }
 }

Finally we implement the creator of the proxy factory in the same file:

public class ExtendedWithNotifyProxyFactoryFactory :NHibernate.Bytecode.IProxyFactoryFactory
{
    public IProxyFactory BuildProxyFactory()
    {
        return new DataBindingNotifyPropertyProxyFactory();
    }
}

And we are done! To use the new interceptor, when inititalizing NHibernate we add the following (line needed in red color):

Configuration cfg = new Configuration();
cfg.AddAssembly("NHibernateTests");
cfg.Properties[NHibernate.Cfg.Environment.ProxyFactoryFactoryClass] = typeof(ExtendedWithNotifyProxyFactoryFactory).AssemblyQualifiedName;
_sessionFactory = cfg.Configure().BuildSessionFactory();

Now we can work with proxies and WPF receives the appropriate PropertyChanged events when properties change. Whenever we want to disable this functionality we just omit the line that adds it in the configuration.

If you just need to grab the code and keep working download the following .cs file, add it to your project, add the References and the configuration in the previous paragraph (in red) and you are good to go:

NotifyPropertyChangedInterceptor.zip

This solution-code was based and insipired by the following articles:

Castle Dynamic Proxy tutorial by Krzysztof Ko┼║mic
Extending NHibernate Proxies by Ayende Rahien
The discussion started by Arthur Dorochowicz

kick it on DotNetKicks.com

Shout it

blog comments powered by Disqus
hire me