代码改变世界

NHibernate学习、使用心得小结帖

2008-11-03 10:46  JimLiu  阅读(2989)  评论(3编辑  收藏  举报

本文会持续更新

最近学习和使用NHibernate,使用的版本是NHibernate 2.0.1 GA,中间还算有不少收获,都是细节,系列教程里一般不会有的,在这里记录下。

  1.  对于one-to-many的配置是相当繁琐的,如果你的数据库中外键逻辑上是无约束的(也就是one端不存在,many端也可存在,比如有一张自关联的表,你用ParentID=0来表示是根级,那么ID=0的记录可能是不存在的),但实际上是约束的键,那么这时候你直接插入many端会被数据库报错的。对于这个问题,我提出两种解决方案:第一,把外键设置为无约束的,这样可能会损失一些级联删除、更新的功能;第二,添加一个ID为0的记录作为全部记录的的根节点。——我推荐采用2方案。
  2. 还是one-to-many的问题:在one-to-many的配置中,对于many端,.hbm.xml中的<many-to- one>结点一定要小心配置,首先是not-null要指定,同时应该把insert置为true——说实话,这两个我不是很肯定,有心的朋友希望 能实验下。这不算啥,最值得注意的是one端主键的unsaved-value一定要小心!以刚才那个例子,如果你的ID=0是有意义的,那么一定要把 unsaved-value显示指定为-1之类无意义数据,并且修改one端实体类ID的初始值——声明或者在构造方法里都行。否则NH会认为这是空对 象,在你保存many端的时候把外键设置为Null
  3. 还是one-to-many的问题……汗:对one端的ICriteria做CreateCriteria可以join加载many端,但是这样做是inner join的,显然如果这个one端没many端(这个是很正常的,比如文章可能没有回复),这样这条one端记录就不会被选择了。如果我们需要选择,应该使用left outer join(不知道的复习下数据库原理吧,呵呵)。这时候使用ICriteria.CreateCriteria的另一个重载,可以跟上一个枚举参数作为连接类型,这样就爽了。
  4. 在使用many-to-many关系的时候,我们会用一个中间表做过度。通常这个中间表是只作过度用的,我们没必要去定义实体类。但是如果我 们遇到一个这样的表是有其数据的,有意义的实体类,比如你的User-Group是many-to-many关系,同时那个UserInGroup还记录 了某个用户对于那个组有什么特殊的权限,或者什么时候加入啊这类的数据,那么它就是有意义的了。这时候我们应该为UserInGroup定义联合主键。首 先是定义一个UserInGroupPk这样的联合主键类,把UserInGroup的主键定义为UserInGroupPk,然后再去hbm里配置。这 时候千万要记得必须显式override这个联合主键类的Equals, GetHashCode和ToString方法,我也不知道为什么,反正不这样NH就不让你映射成功。
  5. ICriteria可以投影,其中有RowCount方法,相当于count(*),在判断是否存在对象的时候就不用判断是否能取到了。类似地ICriteria功能甚强,还没研究很多。
  6. 这是一个疑问,不是心得。使用ICriteria可以投影,但是投影了一个类的多个属性以后(比如User有Username, Password, Gender, Address,只投影Username, Password),就只能返回objcet[](使用UniqueResult())或者IList<object[]>(使用List())了;而如果只投影一个属性,则根据这个属性的类型可以使用类似UniqueResult<int>(),List<decimal>()这样的,返回那个投影的唯一列的类型。但是这样就失去了NH的自动绑定到Model对象的功能了,我对此甚是郁闷,不知道还有什么好方法,请有心得的朋友指点。
  7. 接着上一条:后来我到网上查了查,原来国外有人已经做了一个类似的东西,用反射的,虽然性能下降,不过可以说是用C#时间换数据库时间和最宝贵的IO,我觉得是值的,基本的思路是实现了个NHibernate.Transform.IResultTransformer接口,并且通过ICriteria.SetResultTransfomer注入到ICriteria中去。这样做有两个比较大的弊端——第一、使用反射构建对象,性能有不小损失;第二、投影出来的只能是属性,不能是组函数(虽然说这么想就太贪心了),不过相信还是有其用武之地的。后来我索性写成了Extension Method,代码如下:
    Code
    那篇文章的原文
  8. 合理地配置log4net不仅可以让我们更了解NHibernate在干什么,还可以帮助我们轻松地为项目搭建数据层。有时间我再总结一下log4net的使用新的吧。
  9. 随用one-to-one关系(虽然不推荐这么做),默认地,延迟加载是被关闭的,必须把主要端(比如User-Profile中的User端)的one-to-one的constrained设置为true,这样才能保证NHibernate认为你自己会保证参照完整性,而不是它通过inner join来保证,这样就实现延迟加载了。如果需要立即加载,可以用NHibernateUtil.Initialize或者用Criteria API来做。这时候你会发现加载次要端的时候NHibernate又来了个inner join,如法炮制在次要端的one-to-one里把constrained也设置为true,一切OK。
  10. 使用ISession.Get和Load是有差别的,大概就是Get在任何情况下都会和Database同步,而Load会优先考虑ISession的会话级proxy一级缓存。使用Criteria API查询在任何情况下都得不到一级缓存(至少我没发现怎么才能得到),而二级缓存由于NHibernate.Caches项目迟迟未发布支持2.0.1 GA的版本,所以我没能测试。
  11. 使用IInterceptor接口可以定义SessionFactory级别或者Session级别的事件拦截器,借此可以帮助我们面向切面(AOP)地对NHibernate的操作进行一些监管和干预。IInterceptor接口是一个比较庞大的接口,为了避免一次性实现很多方法,我们可以实现自己的类并且让它继承自EmptyInterceptor,这是一个以空方法体实现了IInterceptor的所有方法的适配器类,我们只需要重写其中一部分自己需要的方法就可以使用了。我尝试通过OnLoad来监控Session执行SQL的次数,未果,因为OnLoad是在加载每一个实体类对象的时候发生的,也就是说如果一条查询得到了100条记录,它就会发生100次OnLoad,至于怎么统计SQL的次数,我还在研究当中。
  12. 借用经典的Customer - Order模型,举个例子。这是一个one - to - many的关系,其中我们都打开了lazy="true"。举这个例子是为了说明NHibernate的延迟加载机制是非常棒的。首先我们加载Customer[1]这个实例,用ISession.Load,这时会发现没有执行SQL,直到访问了1号客户的非主键属性(因为主键我们已经知道啦)的时候,才会执行SQL,这是ISession级别的缓存在帮忙,使用Load方法有这个惠顾,而使用Get没有,这个区别前面已经说到了。接着我们来个ISession.CreateQuery("from Order o").List<Order>();这时候执行了SQL,我们取出了所有的Order,试想一下有时候会出现这样的情况,我们加载了Order的列表,并且需要列出每条Order及它的Customer的信息,当我们只需要知道Customer的主键(比如用ID,GUID)的时候我们是不用关联加载的,因为这是Order的外键。在关系模型中这很自然,但是在OO模型中就不自然了,我们要以外键实体而非外键列的形式暴露关系,所以Order中我们只能有Customer对象而不能有CustomerId属性。这时候尝试访问每一条Order.Customer.CustomerId,发现没有执行SQL,因为访问的是Costomer的主键,也就是Order中的外键,是不需关联的,所以自然不会发生SQL执行。当我们尝试访问Order.Customer.Firstname的时候发现SQL执行了,这是因为NHibernate发现了你在访问one端,所以自动执行了延迟的加载。再往下,发现加载过的Customer,包括刚才那个Customer[1],只要加载过的对象就不会被加载第二次了,更神奇的是只要主键相同的两个one端对象都会是ReferenceEqual的!NHibernate是怎么知道我们在访问one端或者many端的属性呢?经过观察就能发现其实加载Order列表的时候,每个Order的Customer属性都不是Customer的POCO类,而是一个CustomerProxy,NHibernate构建了动态代理,这就是为什么我们需要延迟加载的时候一定要把属性设置为Virual的原因。
  13. 终于到SVN上去把传说中的NHibernate.Linq弄下来了,源代码居然能编译!而且Dependency里的NHibernate.dll的版本是2.1.0!也许不久2.1.0就会浮出水面。二话不说先玩了玩Linq,Test里的[Ignore("TODO")]项还是比较多的,Northwind访问起来也问题多多,不过Linq用着实在是爽。主要的问题还是集中在Join, GroupJoin, GroupBy等比较“高级”的关系操作。但是对于one-to-many和many-to-many,不复杂的查询还是可以玩玩。目前也没看到官方有关于2.1.0的消息,beta都没见更别提RC->GA遥遥无期了,Linq还有时间继续,SVN上的Age是2 weeks,看来活跃度一般,不知道到了NHibernate 2.1.0正式发布的时候Linq能不能真正成为其一部分,期待期待再期待。
  14. 还是Get和Load的问题,Load有一个陷阱,就是当数据不存在的时候,Load是不会返回null的,而是一个普普通通的代理类——换句话说,Load永远不会返回null,所以当用Load的时候是无法通过obj == null来判断这条数据是否存在的。但是Get是可以返回null的,所以当需要用obj == null判断数据是否存在的时候,用Load就会bug了。