[NHibernate]用一个实体类对应多个数据库表

首先谈一下背景。最近正要上马的项目中,遇到一个客户的需求:表名是动态的,根据数据库里的某些值来决定。举个例子来说

 

需求

有主表Student,有两列ID和Class

然后有从表XXXStudentDetail,其中XXX是Student表中的Class的值,取值范围不受限

也就是说每有一种Class就要添加一张StudentDetail表

 

面对需求,技术上考虑了两个方案,但是都碰壁了

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

Child.hbm.xml


 

ChildDetail.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<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如下

Interceptor
    public class EntityNameInterceptor : EmptyInterceptor
    {
        
public override string GetEntityName(object entity)
        {
            var entityNameEntity 
= entity as IEntityNameEntity;
            
return null == entityNameEntity ? base.GetEntityName(entity) : entityNameEntity.EntityName;
        }
    }

 

 

在使用上,既可以调用Configuration.SetInterceptor方法, 也可以在OpenSession时作为参数传入,例如

        public ISession GetSession()
        {
            
return SessionFactory.OpenSession(new EntityNameInterceptor());
            
//return SessionFactory.OpenSession();
        }

 

 

当然,我的ChildDetail类也要相应的实现IEntityNameEntity接口,在EntityName属性的get方法中返回hbm文件中定义的值 

ChildDetail
    public class ChildDetail : Child, IEntityNameEntity
    {
        
public virtual string Name { getset; }
        
public virtual DateTime EnrolmentDate { getset; }
        
public virtual string ClassName { getset; }

        
public virtual string EntityName
        {
            
get { return string.Format("{0}ChildDetail", FullClassName); }
        }
    }

 

 

核心的实现就是如上了,我的同事可以在svn的\Learning\NHibernate\src\DynamicModel下找到全部代码。

本文大量参考了这篇博文, 受益匪浅,深表感谢。这位博主的实现还有两点有意思的地方

一是他用一个实体类表示了三个有继承关系的表,这跟普通的一个表表示一棵继承树好像正相反

二是还顺便介绍了dynamic component的用法

各位感兴趣不妨也去看看。

posted @ 2010-07-06 15:02  jiaxingseng  阅读(2604)  评论(1编辑  收藏  举报