[NHibernate]用一个实体类对应多个数据库表
首先谈一下背景。最近正要上马的项目中,遇到一个客户的需求:表名是动态的,根据数据库里的某些值来决定。举个例子来说
面对需求,技术上考虑了两个方案,但是都碰壁了
1 在NamingStrategy上做文章,但是可重载的ClassToTableName方法传入的是Class名而不是object
2 在运行时动态生成mapping信息,但是NH的SessionFactory是有Configuration.BuildSessionFactory得到的,之后无法更改Configuration了(笔者也曾经尝试用反射强行生成mapping,插入到SessionFactory中的各个Dictionary中,但是一来工作量巨大,二来不了解NH的内部结构只得作罢)
顺便说一句也曾经考察过Linq To SQL能否做到,看到了msdn上的这段讨论, 觉得可以创建一个abstract的类用Attribute的方式来mapping StudentDetail表,其中TableName不指定,在运行时使用emit生成该类的子类,来map具体的某张表。但是问题在于可重载的GetTable方法的传入参数是Type而不是object,属于和NH的NamingStrategy路线卡在了一样的地方
后来好在客户比较通融,接受了我们把XXX部分hard code的方案(客户也相应的把XXX的取值范围从200左右缩减到了5个,不然hard code也会死人的。。。)
于是技术上的需求变成了一个实体类对应几个mapping,于是祭出我们今天的主角entity name
entity name是NH2.1开始出现的新tag。官网上给出了一个实例,是用一个泛型类对应几个mapping。
下面就看看mapping
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="SomeCompany.MySchool.Persistent.Model" assembly="SomeCompany.MySchool.Persistent.Model" default-lazy="true">
<joined-subclass name="ChildDetail" extends="Child" entity-name="Year2009Class1ChildDetail" table="Year2009Class1ChildDetail">
<key column="Id" not-null="true" />
<property name="Name" type="AnsiString" length="32"/>
<property name="EnrolmentDate" type="System.DateTime" not-null="true"/>
<property name="ClassName" type="AnsiString" length="32" not-null="true"/>
</joined-subclass>
<joined-subclass name="ChildDetail" extends="Child" entity-name="Year2009Class2ChildDetail" table="Year2009Class2ChildDetail">
<key column="Id" not-null="true" />
<property name="Name" type="AnsiString" length="32"/>
<property name="EnrolmentDate" type="System.DateTime" not-null="true"/>
<property name="ClassName" type="AnsiString" length="32" not-null="true"/>
</joined-subclass>
<joined-subclass name="ChildDetail" extends="Child" entity-name="Year2010Class1ChildDetail" table="Year2010Class1ChildDetail">
<key column="Id" not-null="true" />
<property name="Name" type="AnsiString" length="32"/>
<property name="EnrolmentDate" type="System.DateTime" not-null="true"/>
<property name="ClassName" type="AnsiString" length="32" not-null="true"/>
</joined-subclass>
</hibernate-mapping>
可以看到我们给每个joined subclass指定了对应的表和entity name。这个entity name是怎么使用的呢?实际上我们需要创建自己的interceptor如下
{
public override string GetEntityName(object entity)
{
var entityNameEntity = entity as IEntityNameEntity;
return null == entityNameEntity ? base.GetEntityName(entity) : entityNameEntity.EntityName;
}
}
在使用上,既可以调用Configuration.SetInterceptor方法, 也可以在OpenSession时作为参数传入,例如
{
return SessionFactory.OpenSession(new EntityNameInterceptor());
//return SessionFactory.OpenSession();
}
当然,我的ChildDetail类也要相应的实现IEntityNameEntity接口,在EntityName属性的get方法中返回hbm文件中定义的值
{
public virtual string Name { get; set; }
public virtual DateTime EnrolmentDate { get; set; }
public virtual string ClassName { get; set; }
public virtual string EntityName
{
get { return string.Format("{0}ChildDetail", FullClassName); }
}
}
核心的实现就是如上了,我的同事可以在svn的\Learning\NHibernate\src\DynamicModel下找到全部代码。
本文大量参考了这篇博文, 受益匪浅,深表感谢。这位博主的实现还有两点有意思的地方
一是他用一个实体类表示了三个有继承关系的表,这跟普通的一个表表示一棵继承树好像正相反
二是还顺便介绍了dynamic component的用法
各位感兴趣不妨也去看看。