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

NHibernate & INotifyPropertyChanged的更多相关文章

  1. Nhibernate的Session管理

    参考:http://www.cnblogs.com/renrenqq/archive/2006/08/04/467688.html 但这个方法还不能解决Session缓存问题,由于创建Session需 ...

  2. "NHibernate.Exceptions.GenericADOException: could not load an entity" 解决方案

     今天,测试一个项目的时候,抛出了这个莫名其妙的异常,然后就开始了一天的调试之旅... 花了很长时间,没有从代码找出任何问题... 那么到底哪里出问题呢? 根据下面那段长长的错误日志: -- ::, ...

  3. nhibernate连接11g数据库

    我框架的数据映射用 nhibernate连接多数据库,这次又增加了oracle11g,负责开发的同事始终连接不上,悲催的sharepoint调试是在不方便... 下面描述下问题的解决,细节问题有3个: ...

  4. 全自动迁移数据库的实现 (Fluent NHibernate, Entity Framework Core)

    在开发涉及到数据库的程序时,常会遇到一开始设计的结构不能满足需求需要再添加新字段或新表的情况,这时就需要进行数据库迁移. 实现数据库迁移有很多种办法,从手动管理各个版本的ddl脚本,到实现自己的mig ...

  5. 跟我学习NHibernate (1)

    引言:Nibernate概述 NHibernate是一个ORM框架,NHibernate是一个把C#对象世界和关系世界数据库之间联系起来的一座桥梁.NHibernate 能自动映射实体模型到数据库,所 ...

  6. 让OData和NHibernate结合进行动态查询

    OData是一个非常灵活的RESTful API,如果要做出强大的查询API,那么OData就强烈推荐了.http://www.odata.org/ OData的特点就是可以根据传入参数动态生成Ent ...

  7. MVC Nhibernate 示例

    首先,非常感谢提出问题的朋友们,使得本人又去深入研究了NHibernate的<Session-Per-Request 模式>.   前言: 谈到NHibernate大伙并不陌生,搞Java ...

  8. Nhibernate mapping 文件编写

    生成工具软件 现在生成工具软件有很多了,例如商业软件:NMG.CodeSmith.Visual NHibernate,开源软件:MyGeneration.NHibernate Modeller.AjG ...

  9. NHibernate之映射文件配置说明

    NHibernate之映射文件配置说明 1. hibernate-mapping 这个元素包括以下可选的属性.schema属性,指明了这个映射所引用的表所在的schema名称.假若指定了这个属性, 表 ...

随机推荐

  1. Hadoop项目实战

    这个项目是流量经营项目,通过Hadoop的离线数据项目. 运营商通过HTTP日志,分析用户的上网行为数据,进行行为轨迹的增强. HTTP数据格式为: 流程: 系统架构: 技术选型: 这里只针对其中的一 ...

  2. VS2017 Use Git Push To TFS2018 Failure

    先上图: 提示信息很明确,认证失败!! 在使用TFS2018 建立Git Repo 的时候,有一句提示,如果遇到权限问题,请升级Git,我本地Git已经是最新版本,并且在环境变量中,如下图 经过分析觉 ...

  3. Android开发人员必须掌握的10 个开发工具+应该深入学习的10个开源应用项目

    一.Android开发人员必须掌握的10 个开发工具 Android SDK 本身包含很多帮助开发人员设计.开发.测试和发布 Android 应用的工具,在本文中,我们将讨论 10 个最常用的工具. ...

  4. 【Raspberry pi】set up an ftp server

    http://www.debian-administration.org/articles/228 As a means of distributing large collections of fi ...

  5. Vimium、CrxMouse配置信息

    每次使用别的地方的Chrome的时候,虽然Vimium插件能同步过来,但是配置信息不在,所以先记录在整理以备不时之需. 这个是Vimium的配置信息,然后我还会把搜索引擎改为http://www.ba ...

  6. Mybatis 二级缓存脏读

    脏读的产生 Mybatis的二级缓存是和命名空间绑定的,所以通常情况下每一个Mapper映射文件都有自己的二级缓存,不同的mapper的二级缓存互不影响.这样的设计一不注意就会引起脏读,从而导致数据一 ...

  7. 【BZOJ4367】[IOI2014]holiday假期 分治+主席树

    [BZOJ4367][IOI2014]holiday假期 Description 健佳正在制定下个假期去台湾的游玩计划.在这个假期,健佳将会在城市之间奔波,并且参观这些城市的景点.在台湾共有n个城市, ...

  8. 170206、sping注解@autowired和@resource的区别

    新年第一天上班,新的一年,我们17加油!!! @Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自动注入罢了 ...

  9. LeetCode 学习

    1.整数反转 题目:给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 思路:把最后的一位提取出来,放到新的容器前面,反复进行上面的操作,同时也要判断是否会导致溢出 class ...

  10. tomcat的虚拟目录映射常用的几种方式

      我们在项目部署的时候,可以采用多种方式,接下来我们将在实际中比较常用的几种方式总结如下. 1.可以直接将我们的项目丢到tomcat的webapps目录下,这样当tomcat重启的时候,我们就可以访 ...