[NHibernate]用一个实体类对应多个数据库表(二)
上一次我们看到了如何让NHibernate通过写多个mapping“半自动”的实现我们动态映射的需求。
这一次我们看一下如何实现多对一之类的关系,以及如何存取数据。
1 两张XXX表存在多对一之类关系时的映射
由于只指定Class,NHibernate还是不知道该Class具体应该映射到哪张表,于是需要同样的指定entity name
<id name="Id" type="Guid" column="uidChildHistoryKey">
<generator class="guid.comb"/>
</id>
...
<many-to-one name="Child" class="ChildEntity" entity-name="MFSChildEntity" column="uidChildKey" not-null="true"/>
...
</class>
2 如何Save或者Update对象
NHibernate的ISession接口提供了Save(Update等方法同样)的重载方法,可以指定entity name
但是由于我们已经在Interceptor中拦截了GetEntityName方法,所以这里我们可以像以前一样调用ISession.Save(object entity)
3 如何检索对象
由于检索的时候NHibernate无法由类名直接推知表名了,所以我们必须手动的指定entity name了
在使用ISession.Load的时候我们需要使用ISession.Load(string entityName, object id)方法
在使用Criteria的时候,我们需要在CreateCriteria的时候传入entityName
在使用HQL的时候,我们也需要从原来的from ClassName变为from EntityName
大概就是这样吧。
接下来的研究方向是通过一套模板的mapping自动生成各个可能的XXX专用mapping,大概思路就是把那些xml反序列化,然后Clone出来几份,replace一些东西之后再序列化,准备接下来看看NHibernate的源码,看看它的hbm2ddl里面有没有什么现成可用的东西~~
UPDATE:HQL貌似行不通,至少我还没有找到解决之道。具体如下
忽然发现一个问题:以下的UT代码,忽然发现跑不通了
public void SaveChild()
{
var child = new ChildEntity
{
IdNo = "S1234567D",
IdType = ChildIdType.NRIC,
...
};
ChildDAO.Save(child);
Assert.AreNotEqual(Guid.Empty, child.Id);
Assert.IsNotNull(LoadByIdTypeAndNo(child.Centre.BU, child.IdType, child.IdNo));
}
public ChildEntity LoadByIdTypeAndNo(string bu, ChildIdType idType, string idNo)
{
return ChildDAO.FindByIdTypeAndNo(bu, idType, idNo);
}
看了一下NH生成出的sql,吓了一跳
NHibernate: select ... from tblMFSChildren mfschilden0_ where mfschilden0_.chrIdType=@p0 and mfschilden0_.strIdNo=@p1;@p0 = 'N', @p1 = 'S1234567D'
NHibernate: select ...from tblCCChildren ccchildent0_ where ccchildent0_.chrIdType=@p0 and ccchildent0_.strIdNo=@p1;@p0 = 'N', @p1 = 'S1234567D'
NHibernate: select ... from tblLSHChildren lshchilden0_ where lshchilden0_.chrIdType=@p0 and lshchilden0_.strIdNo=@p1;@p0 = 'N', @p1 = 'S1234567D'
这其中MFS、CC、LSH是我的XXX的三种可能的取值
回头看一下DAO的写法,并没有select三个表
{
var query = string.Format("select c from {0}ChildEntity as c where c.ChrIdType=:idType and c.IdNo=:idNo", bu);
return Session.CreateQuery(query)
//.SetEnum("idType", idType)
.SetCharacter("idType", (char)idType)
.SetAnsiString("idNo", idNo)
.SetMaxResults(1)
.UniqueResult<ChildEntity>();
}
回想一下从上次跑通UT到现在UT失败,做过的工作正是把mapping从一份(MFS)增加到了上述的三份(关于如何做到这一点还请期待下文)。
于是问题大概出在NH在翻译HQL的时候,把from entityName的部分首先翻译成了className,然后发现有三个entity对应着同一个className,于是生成出三条sql语句。但是如果直接指定成from className的话,又会报错说没有className对应的mapping。
找了半天IQuery的方法,也没有什么能够手动指定entityName的地方,无奈放弃HQL。。。哪位知道方法还望不吝赐教
不过反正也早就瞅HQL不顺眼了~~又不能获得编译器的检查,又容易写错(例如少写一个空格之类的)。但是Criteria API我还是不会用(这玩意可读性也太差了吧。。。)没关系,祭出早就想试一试的LinqToNhibernate~~
{
return Session.Linq<ChildEntity>(bu + ChildEntity.StaticEntityClassName)
.Where(child => child.ChrIdType == (char)idType
&& child.IdNo.Equals(idNo, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
}
恩~~简单顺眼~~UT的结果也正确,就是不知性能如何~~~~以后再说吧~咳咳~~
P.S.1 ChrIdType本来在我的ChildEntity是protect virtual的因为我不希望外接能直接操作这个char字段,而是希望通过一个枚举来访问,但是现在不得不public出来了。解决办法么,一个是考虑到.Net世界里印象中提供一种FriendsAssembly机制可以让我的DAO的Assembly内部访问ModelAssembly中的类的internal字段,可以用这种方法解决(话说我都想吐槽我自己了,上述所说都是在正常的良好分层的架构下的考虑,而这次应客户的要求DAO(包括interface和impl)和Model是定义在同一个叫做DataAccess的Assembly,只需要protect internal就可以了(光internal还不行,NH不干)。。。客户要求多原来还有这种好处orz)。另一个更好的方向是直接使用NH提供的EnumStringType,这个今天刚看到,还没有尝试。
P.S.2 在初次使用LinqToNhibernate的时候发现NHibernate.Linq.dll竟然没有强命名,这是何等的师太= =
Google了一下,找到Signer这个东西,给创建出的新的强命名过的dll就好了
最后吐槽一下Nhibernate,官方网站上挂的那个说明文档太老了吧!NH 2.1的特性就没怎么提啊~~~