[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 {  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的用法

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

你可能感兴趣的:(Hibernate)