Lesser-Known NHibernate Features: Proxy Generation
Did you know that you can leverage NHibernate’s built-in proxy generator to inject custom behavior in your classes? It is called NHibernate.Proxy.DynamicProxy.ProxyFactory and there’s an interface, NHibernate.Proxy.DynamicProxy.IInterceptor, that you can use to extend it, by intercepting method and property calls.
First, you inject an NHibernate interceptor, inheriting from EmptyInterceptor, in the configuration instance, before building the session factory:
1: public sealed class NotifyPropertyChangedInterceptor : EmptyInterceptor
2: {
3: private static readonly ProxyFactory factory = new ProxyFactory();
4:
5: private ISession session = null;
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: var entityType = this.session.SessionFactory.GetClassMetadata(clazz).GetMappedClass(entityMode);
16: var target = this.session.SessionFactory.GetClassMetadata(entityType).Instantiate(id, entityMode);
17: var proxy = factory.CreateProxy(entityType, new _NotifyPropertyChangedInterceptor(target), typeof(INotifyPropertyChanged));
18:
19: this.session.SessionFactory.GetClassMetadata(entityType).SetIdentifier(proxy, id, entityMode);
20:
21: return (proxy);
22: }
23: }
The code in the Instantiate method will add the INotifyPropertyChanged interface to the generated proxy instance, a common interface that is used for detecting property changes.
But the real fun is in the NHibernate.Proxy.DynamicProxy.IInterceptor implementation:
1: sealed class _NotifyPropertyChangedInterceptor : NHibernate.Proxy.DynamicProxy.IInterceptor
2: {
3: private PropertyChangedEventHandler changed = delegate { };
4: private readonly Object target = null;
5:
6: public _NotifyPropertyChangedInterceptor(Object target)
7: {
8: this.target = target;
9: }
10:
11: #region IInterceptor Members
12:
13: public Object Intercept(InvocationInfo info)
14: {
15: Object result = null;
16:
17: if (info.TargetMethod.Name == "add_PropertyChanged")
18: {
19: var propertyChangedEventHandler = info.Arguments[0] as PropertyChangedEventHandler;
20: this.changed += propertyChangedEventHandler;
21: }
22: else if (info.TargetMethod.Name == "remove_PropertyChanged")
23: {
24: var propertyChangedEventHandler = info.Arguments[0] as PropertyChangedEventHandler;
25: this.changed -= propertyChangedEventHandler;
26: }
27: else
28: {
29: result = info.TargetMethod.Invoke(this.target, info.Arguments);
30: }
31:
32: if (info.TargetMethod.Name.StartsWith("set_") == true)
33: {
34: var propertyName = info.TargetMethod.Name.Substring("set_".Length);
35: this.changed(info.Target, new PropertyChangedEventArgs(propertyName));
36: }
37:
38: return (result);
39: }
40:
41: #endregion
42: }
This will detect calls to properties (starting with set_) and, after they complete, will raise the PropertyChanged event. This will save you lots of lines in your code!
You just need to register the NHibernate interceptor, and your done:
1: cfg.SetInterceptor(new NotifyPropertyChangedInterceptor());
And from now one, all your entities automagically implement INotifyPropertyChanged:
1: var product = session.Get<Product>(1);
2: var npc = product as INotifyProductChanged;
3: npc.PropertyChanged += (s, e) =>
4: {
5: if (e.PropertyName == "Price")
6: {
7: Console.WriteLine("Someone changed the price of the product!");
8: }
9: };