NHibernate & INotifyPropertyChanged

One of the things that make NHibernate easy to use is that it fully support the POCO model. But one of the things that most people do not consider is that since NHibernate did the hard work of opening up the seams to allow external persistence concerns, we can use the same seams to handle similar infrastructure chores externally, without affecting the POCO-ness of our entities.

Allow me to show you what I mean. First, we create the following factory, which makes use of Castle Dynamic Proxy to weave in support for INotifyPropertyChanged in a seamless manner:

public static class DataBindingFactory
{
	private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator();

	public static T Create<T>()
	{
		return (T) Create(typeof (T));
	}

	public static object Create(Type type)
	{
		return ProxyGenerator.CreateClassProxy(type, new[]
		{
			typeof (INotifyPropertyChanged),
			typeof (IMarkerInterface)
		}, new NotifyPropertyChangedInterceptor(type.FullName));
	}

	public interface IMarkerInterface
	{
		string TypeName { get; }
	}

	public class NotifyPropertyChangedInterceptor : IInterceptor
	{
		private readonly string typeName;
		private PropertyChangedEventHandler subscribers = delegate { };

		public NotifyPropertyChangedInterceptor(string typeName)
		{
			this.typeName = typeName;
		}

		public void Intercept(IInvocation invocation)
		{
			if(invocation.Method.DeclaringType == typeof(IMarkerInterface))
			{
				invocation.ReturnValue = typeName;
				return;
			}
			if (invocation.Method.DeclaringType == typeof(INotifyPropertyChanged))
			{
				var propertyChangedEventHandler = (PropertyChangedEventHandler)invocation.Arguments[0];
				if (invocation.Method.Name.StartsWith("add_"))
				{
					subscribers += propertyChangedEventHandler;
				}
				else
				{
					subscribers -= propertyChangedEventHandler;
				}
				return;
			}

			invocation.Proceed();

			if (invocation.Method.Name.StartsWith("set_"))
			{
				var propertyName = invocation.Method.Name.Substring(4);
				subscribers(invocation.InvocationTarget, new PropertyChangedEventArgs(propertyName));
			}
		}
	}
}

Now that we have this, we can start creating entities that support INotifyPropertyChanged simply by calling:

var customer = DataBindingFactory.Create<Customer>();

This customer instance supports change notifications, but it is not something that we had to do, and it is something that we can pick & choose. If we want to use the same entities in a different context, where we don’t need INPC, we can simply skip using the factory, and not deal with it at all.

Now, using the data binding factory is good when we create the instances, but how are we going to teach NHibernate that it should use the factory when creating entities? That is actually quite easy, all we need to do is write an interceptor:

public class DataBindingIntercepter : EmptyInterceptor
{
	public ISessionFactory SessionFactory { set; get; }

	public override object Instantiate(string clazz, EntityMode entityMode, object id)
	{
		if(entityMode == EntityMode.Poco)
		{
			Type type = Type.GetType(clazz);
			if (type != null)
			{
				var instance= DataBindingFactory.Create(type);
				SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance,id, entityMode);
				return instance;
			}
		}
		return base.Instantiate(clazz, entityMode, id);
	}

	public override string GetEntityName(object entity)
	{
		var markerInterface = entity as DataBindingFactory.IMarkerInterface;
		if (markerInterface != null)
			return markerInterface.TypeName;
		return base.GetEntityName(entity);
	}
}

Note that this interceptor does two things, first, it handles instantiation of entities, second, it make sure to translate data binding entities to their real types. All we are left now is to set the interceptor and we are done. Again, this is an opt in option, if we don’t want it, we can just not register the proxy, and we don’t  worry about it.

Something that will probably come up is why not use NHibernate’s own proxy generation (byte code provider) to provide this facility. And the answer is that I don’t think it would be as easy as that. The byte code provider is there to provider persistence concerns, and trying to create a byte code provider that does both that and handle INPC issues is possible, but it would be more complicated.

This is a simple and quite elegant solution.

Tags:

Posted By: Ayende Rahien

Published at Fri, 07 Aug 2009 19:50:00 GMT

Tweet

Share

Comments Feed

Comments

08/07/2009 08:50 PM by Krzysztof Ko?mic

This is like 5th impl of INPC via DynamicProxy I‘ve seen in last month :) Seems everyone is doing it. Anyway, this is a great example, although I would split all the logic you put into the interceptor, between InterceptorSelector, the interceptor and use mixin for the actual INPC implementation. I understand however that you probably did it in one place to keep the example short.

08/07/2009 09:18 PM by Dmitry

I really like this implementation. As you said, it is simple and elegant.

Is there a way for NHibernate to instantiate BindingList for POCO properties of type IList ?

08/07/2009 09:28 PM by Frank Quednau

I‘ve used a similar approach on a project last year. It was more of a mapping between a ViewModel described just as an interface and some POCO. The interface would implement INPC and its nature allowed us to do funny things like declaratively adding Undo, Commit & Rollback functionality towards an associated POCO.

08/07/2009 10:26 PM by Tuna Toksoz

Here is a post on the same issue, he also has INotifyCollectionChanged etc. very cool posts.

jfromaniello.blogspot.com/.../...anged-as-aop.html

08/08/2009 01:18 PM by Ayende Rahien

Dmitry,

Yes, sort of. You would need to write a accessor, take a look at how NHibernate Generics was implemented.

08/08/2009 02:30 PM by José Romaniello

Besides of what Tuna said, I implement the code in unhaddins.wpf to work with INotifyPropertyChanged, INotifyCollectionChanged, IEditableObject (two implementatios). For InotifyPropertyChanged my IInterceptor is very similar (although I have another interceptor for the entity name thing)... and I use another extension point of nhibernate to inject the proxy...

08/10/2009 10:39 PM by Jernej Logar

One question. How does this way of creating entities go together with aggregates (as in DDD)? You can‘t have the agg POCO create a proxied POCO this way.

08/10/2009 11:45 PM by Ayende Rahien

Jernej,

I have no idea, ISTR that DDD mentions factories, but regardless, I don‘t care about DDD in this context.

08/15/2009 07:23 PM by Glenn Block

Nice post Oren! I like the way you went further and added nested subscriptions. One thing though, this requires all the props to be virtual.

Another idea I had been toying with to some success was to use mixins to create a proxy that delegates to a poco rather than deriving from it.. This way the poco does not need to have virtual props. You could take the approach further to then allow specifying exactly which members are exposed in the ViewModel rather than automatically adding all of the members to the VM surface.

Any thoughts on this?

Glenn

08/15/2009 08:08 PM by Ayende Rahien

Glenn,

Virtuals are not an issue, NH already has that requirement.

Type wrapping is actually something we are considering for DP 3.0

The problem is with instance management and leaking this at that point.

08/18/2009 01:54 AM by Jon Masters

I‘m really excited about getting this to work, but i‘ve run into a problem implementing. My domain objects are in a different project from my repositories, which I believe causes all the Type.GetType(clazz) to return null.

I was able to get around it by using

    public Type FindType(string typeName)

    {

        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())

        {

            Type foundType = assembly.GetType(typeName);

            if (foundType != null)

                return foundType;

        }

        return null;

    }

but

SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance, id, entityMode);

errors with a null reference exception. I tried to switch from clazz, to type that was returned from FindType, but same result.

Any ideas?

08/18/2009 02:02 AM by Ayende Rahien

Jon,

Not off the top of my head.

Try creating a small test case that shows this.

08/18/2009 02:04 PM by Jon Masters

Turns out the issue was that SessionFactory on the DataBindingInterceptor was null. I had been setting the interceptor in the fluent buildup of the factory

.ExposeConfiguration(config=>config.SetInterceptor(new DataBindingInterceptor()))

Instead I modified my OpenSession method to:

IInterceptor dataBinding = new DataBindingInterceptor {SessionFactory = factory};

        return factory.OpenSession(dataBinding)

Works great now.

09/10/2009 04:43 PM by Chris Holmes

Jon,

All you need to do is add one line of code to your Fluent Buildup. Here‘s what mine looks like:

var intercepter = new DataBindingIntercepter();

            SessionFactory = Fluently.Configure()

                .Database(MsSqlConfiguration.MsSql2005

                              .ConnectionString(x => x.FromConnectionStringWithKey("Movies"))

                              .DefaultSchema("dbo")

                              .ShowSql

                              )

                .Mappings(m => m.FluentMappings.AddFromAssemblyOf

<datasource())

                .ExposeConfiguration(x => x.SetInterceptor(intercepter))

                .BuildSessionFactory();

            intercepter.SessionFactory = SessionFactory;

That last line sets the SessionFactory on the interceptor.

09/10/2009 04:45 PM by Chris Holmes

Oren,

I ran into a problem with this that is really strange (but maybe makes sense).

When I call Get <t to fetch an object from NHibernate, this works and I can cast it to INotifyPropertyChanged and perform the wire-up. When I use Load <t, this does not work. The DataBindingIntercepter‘s Instantiate() method is not getting called. Now, Load() works quite a bit differently than Get(), as you‘ve pointed out on your blog recently. But I am wondering if there is a way to make it work for Load() as well?

09/10/2009 04:50 PM by Ayende Rahien

Chris,

This is because we aren‘t creating an instance yet.

If you want Load to support it as well you need to provide a ProxyFactoryFactory implementation that will support it.

In other words, there are two places that you need it. In the interceptor (when NH create the actual instance) and in the proxy factory (when NH creates the proxy)

09/10/2009 04:57 PM by Chris Holmes

Okay, that makes sense. I figured it had to be something like that; I am just not very familiar yet with how NHibernate does things yet.

Thanks Oren!

Comments have been closed on this topic.

Search:

Future Posts

  1. Large scale distributed consensus approaches: Computing with a hundred node cluster - 6 hours from now

  2. Large scale distributed consensus approaches: Large data sets - about one day from now
  3. Large scale distributed consensus approaches: Concurrent consistent decisions - 2 days from now
  4. RavenDB Wow! Features presentation - 5 days from now
  5. The process of performance problem fixes with RavenDB - 6 days from now

And 1 more posts are pending...

There are posts all the way to Nov 26, 2014

Stats

  • Posts Count: 5,850

  • Comments Count: 43,708

Recent Comments

  • Casper, You have the Northwind sample in the Tasks > Create sample data. Read all.

    By Ayende Rahien on Live playground for RavenDB 3.0

  • P.s. As an afterthought. It would be nice if the demo had some locked samples that you can read and run, but not alter or del... Read all.

    By Casper on Live playground for RavenDB 3.0

  • Currently running on the previous 2.x version. But now I have to move to version 3! Very impressive. Read all.

    By Casper on Live playground for RavenDB 3.0

  • My machine not crawling through loading silverlight was worth the price of admission! Looks great.Read all.

    By Wyatt Barnett on Live playground for RavenDB 3.0

  • Only caveat is that approach wont work if your resolution is higher and your data grows linearly. Codealike is all about thos... Read all.

    By Federico Lois on Is the library open or not?

Syndication

时间: 2024-11-05 19:05:22

NHibernate & INotifyPropertyChanged的相关文章

[Nhibernate]Nhibernate系列之体系结构

引言 在项目中也有用到过nhibernate但对nhibernate的认识,也存留在会用的阶段,从没深入的学习过,决定对nhibernate做一个系统的学习. ORM 对象-关系映射(OBJECT/RELATION MAPPING,简称ORM),是随着面向对象的软件开发方法发展而产生的.面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统.对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据

NHibernate常见问题及解决方法

NHibernate常见问题及解决方法 曾经学过NHibernate的,但是自从工作到现在快一年了却从未用到过,近来要巩固一下却发现忘记了许多,一个"in expected: <end-of-text> (possibly an invalid or unmapped class name was used in the query)."错误查了好半天终于查到了.这篇文章是我转载的NHibernate的常见错误... hbm.xmlNHibernate文件中版本号可能引起的

Linq To Nhibernate 性能优化(入门级)

最近都是在用Nhibernate和数据库打交道,说实话的,我觉得Nhibernate比Ado.Net更好用,但是在对于一些复杂的查询Nhibernate还是比不上Ado.Net.废话不多说了,下面讲讲Linq To Nhibernate的性能优化. 第一点:应该要分清楚当前代码是在数据库上执行,还是在内存中执行(或者什么时候应该在数据库上做,什么时候应该在内存做) 我们在在做查询的时候,常见的使用方法 上面是使用了Iqueryable接口的,它会把数据先筛完了之后,再返回给我们 这个在数据库里呢

NHibernate框架与BLL+DAL+Model+Controller+UI 多层架构十分相似--『Spring.NET+NHibernate+泛型』概述、知识准备及介绍(一)

原文://http://blog.csdn.net/wb09100310/article/details/47271555 1. 概述 搭建了Spring.NET+NHibernate的一个数据查询系统.之前没用过这两个框架,也算是先学现买,在做完设计之 后花了一周搭建成功了.其中,还加上了我的一些改进思想,把DAO和BLL之中相似且常用的增删改查通过泛型T抽象到了DAO和BLL的父类中,其DAO 和BLL子类只需继承父类就拥有了这些方法.和之前的一个数据库表(视图)对应一个实体,一个实体对应一

使用NHibernate(10) -- 补充(inverse &amp;&amp; cascade)

1,inverse属性的作用: 只有集合标记(set/map/list/array/bag)才有invers属性: 以set为例,set的inverse属性决定是否把对set的改动反应到数据库中去,inverse=false(反应),inverse=true(不反应):默认值是false; one-to-many 和many-to-many都适用: inverse等于false时,对于one-to-many,如果删除"一"方,NH会先执行Update语句来把"多"方

[NHibernate]ISessionFactory配置

系列文章 [Nhibernate]体系结构 引言 在上篇文章学习了orm和nhibernate相关概念,这篇文章主要学习ISessionFactory如何配置. 因为NHibernate被设计为可以在许多不同环境下工作,所以它有很多配置参数.不过,大部分都已经有默认值了.NHibernate.Test.dll包含了一个示例的配置文件app.config,它演示了一些可变的参数. 可编程配置方式 NHibernate. Cfg.Configuration的一个实例代表了应用程序中所有的.Net类到

NHibernate VS IbatisNet

  NHibernate 是当前最流行的 Java O/R mapping 框架Hibernate 的移植版本,当前版本是 1.0 .2 .它出身于sf.net..IbatisNet 是另外一种优秀的 Java O/R mapping框架,当前版本是 1.2 .目前属于 apache 的一个子项目了. 相对 NHibernate " O/R "而言, IbatisNet 是一种" Sql Mapping "的 ORM实现.NHibernate 对数据库结构提供了较为

NHibernate连接oracle报错

NHibernate.Exceptions.GenericADOException:“could not execute query [ select sys_user0_.USERID as USERID1_0_, sys_user0_.LOGINNAME as LOGINN2_0_, sys_user0_.LOGINPASSWORD as LOGINP3_0_, sys_user0_.DEPTID as DEPTID4_0_, sys_user0_.REALNAME as REALNA5_0

MVC+Spring.NET+NHibernate .NET SSH框架整合

在JAVA中,SSH框架可谓是无人不晓,就和.NET中的MVC框架一样普及.作为一个初学者,可以感受到.NET出了MVC框架以后太灵活了(相比之前的web Form),嗯,关于.NET中的MVC框架我就不多说了,推荐这位大神的<MVC知多少系列>http://www.cnblogs.com/sheng-jie/p/6291915.html.下面进入正题,.NET中也有SSH框架,他们分别指MVC+Spring.NET和NHibernate. 其中Spring.NET是典型的IOC框架,类似的还