INotifyPropertyChang{ed,ing} and NHibernate
(Updated: thanks, Tom!)
Here's the scenario: your domain model implements INotifyPropertyChanged and INotifyPropertyChanging, so you want to be notified whenever your properties change. NHibernate has an interceptor model that can be used with, together with code generators, to do this transparently.
First, let's define a custom NHibernate interceptor that is also a Castle Project interceptor:
public
class NotifyPropertyChangeInterceptor : EmptyInterceptor, Castle.Core.Interceptor.IInterceptor{
private static readonly ProxyGenerator proxyGenerator = new ProxyGenerator();
public NotifyPropertyChangeInterceptor(ISessionFactory sessionFactory)
{
this.SessionFactory = sessionFactory;
}
public ISessionFactory SessionFactory
{
set;
private get;
}
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 Object Instantiate(String clazz, EntityMode entityMode, Object id){
if (entityMode == EntityMode.Poco)
{
Type type = Type.GetType(clazz, false);
if (type != null)
{
Object instance = this.create(type);
this.SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance, id, entityMode);
return (instance);
}
}
return (base.Instantiate(clazz, entityMode, id));
}
void Castle.Core.Interceptor.IInterceptor.Intercept(IInvocation invocation)
{
if (typeof(INotifyPropertyChanging).IsAssignableFrom(invocation.Method.DeclaringType) == true)
{
if (invocation.Method.Name.StartsWith("set_") == true)
{
String propertyName = invocation.Method.Name.Substring(4);
FieldInfo field = invocation.InvocationTarget.GetType().BaseType.GetField("PropertyChanging", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);
MulticastDelegate evt = field.GetValue(invocation.InvocationTarget) as MulticastDelegate;
if (evt != null)
{
foreach (Delegate del in evt.GetInvocationList())
{
del.Method.Invoke(invocation.InvocationTarget, new Object [] { invocation.InvocationTarget, new PropertyChangingEventArgs(propertyName) });
}
}
}
}
invocation.Proceed();
if (typeof(INotifyPropertyChanged).IsAssignableFrom(invocation.Method.DeclaringType) == true)
{
if (invocation.Method.Name.StartsWith("set_") == true)
{
String propertyName = invocation.Method.Name.Substring(4);
FieldInfo field = invocation.InvocationTarget.GetType().BaseType.GetField("PropertyChanged", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);
MulticastDelegate evt = field.GetValue(invocation.InvocationTarget) as MulticastDelegate;
if (evt != null)
{
foreach (Delegate del in evt.GetInvocationList())
{
del.Method.Invoke(invocation.InvocationTarget, new Object [] { invocation.InvocationTarget, new PropertyChangedEventArgs(propertyName) });
}
}
}
}
}
private Object create(Type type)
{
return (proxyGenerator.CreateClassProxy(type, new Type [] { typeof(INotifyPropertyChanged), typeof(INotifyPropertyChanging) }, this));
}
}
You class is something like this:
[Serializable]
public class SomeClass : INotifyPropertyChanged, INotifyPropertyChanging
{
public virtual Int32 Id
{
get;
set;
}
public virtual String SomeProperty
{
get;
set;
}
public
virtual event PropertyChangedEventHandler PropertyChanged;public virtual event PropertyChangingEventHandler PropertyChanging;
}
And this is how you would use it:
using
(ISessionFactory factory = cfg.BuildSessionFactory()){
NotifyPropertyChangeInterceptor interceptor = new NotifyPropertyChangeInterceptor(factory);
using (ISession session = factory.OpenSession(interceptor))
using (ITransaction tx = session.BeginTransaction())
{
SomeClass c = session.Load<A>(1);
c.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e) { Console.WriteLine("Property {0} Changed", e.PropertyName); };
c.SomeProperty = "blah";
}