浅析NHibernate一对一映射的延迟加载

在使用NHibernate的过程中也遇到了许多麻烦,不过也得到了不少体会。例如NH的不足之处,我理想中的ORM框架是怎么样的,等等这些,以后有机会也可以慢慢和各位进行讨论。

创新互联建站专注于梅江企业网站建设,响应式网站,商城开发。梅江网站建设公司,为梅江等地区提供建站服务。全流程按需开发,专业设计,全程项目跟踪,创新互联建站专业和态度为您提供的服务

不过这篇文章谈论的其实只是一个小技巧,一个workaround,而且甚至于这个是由于我对NHibernate不够了解而造成的。因此,如果您有更好的做法也请不吝指出。这个问题也就是“如何实现NHibernate一对一映射的延迟加载”。

NHibernate一对一映射问题描述

之前对于问题的描述,其实还有很多额外的要求没有讲清楚,而需要“workaround”的现状,也是这些要求共同形成的。经过尝试,如果放弃其中任何一个(如把主表ID的生成策略从identity改为native),则可能就会有更直接的做法了。这些条件是:

NHibernate一对一映射

主键关联

主表的ID为自增字段

所有字段NOT NULL。

主表和子表设置级联删除

现在的问题,就是在这些条件下,如何实现“获取主表对象时,并不加载其对应的子表数据”,也就是所谓的“延迟加载”。当然,除了能够“延迟加载”以外,还必须可以插入,更新和删除——我也尝试过使用某些特殊的映射方式,可以实现延迟加载,但是却无法插入,这自然也无法满足要求。

为了便于理解和实验,我在这里也将其“具体化”。首先是Model,User和UserDetail,它们是典型的一对一关系:

 
 
 
  1. public class User
  2. {
  3.     public virtual int UserID { get; set; }
  4.     public virtual string Name { get; set; }
  5.     public virtual UserDetail Detail { get; set; }
  6. }
  7. public class UserDetail
  8. {
  9.     public virtual int UserID { get; set; }
  10.     public virtual int Age { get; set; }
  11.     public virtual User User { get; set; }
  12. }

而数据库方面则是一个User表和一个UserDetail表:

 
 
 
  1. CREATE TABLE [dbo].[User](
  2.     [UserID] [int] IDENTITY(1,1) NOT NULL,
  3.     [Name] [nvarchar](50) NOT NULL,
  4.  CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED 
  5. (
  6.     [UserID] ASC
  7. ))
  8. GO
  9. CREATE TABLE [dbo].[UserDetail](
  10.     [UserID] [int] NOT NULL,
  11.     [Age] [int] NOT NULL,
  12.  CONSTRAINT [PK_UserDetail] PRIMARY KEY CLUSTERED 
  13. (
  14.     [UserID] ASC
  15. ))
  16. GO
  17. ALTER TABLE [dbo].[UserDetail]  WITH CHECK ADD
  18. CONSTRAINT [FK_UserDetail_User] FOREIGN KEY([UserID])
  19. REFERENCES [dbo].[User] ([UserID])
  20. ON DELETE CASCADE
  21. GO
  22. ALTER TABLE [dbo].[UserDetail] CHECK CONSTRAINT [FK_UserDetail_User]
  23. GO

User表为主表,主键为UserID,自增。UserDetail为副表,主键为UserID,同时作为外键与User表产生关联。同时,外键上设置了级联删除,也就是在删除User表的纪录时,会自动删除UserDetail的纪录。

对于环境的描述就到这里,如果您想要自己实验的话,可以直接使用这些代码。值得强调一下的是,有些朋友可能会使用NHibernate自动生成数据表,那么请注意严格调整NHibernate的配置,使其与这个环境完全相同。

传统一对一映射

关于一对一映射是否可以延迟加载的问题,我在互联网上找了许多资料。有NHibernate的资料,也有没N的资料。有的资料上说不支持,有的资料却又说可以实现。不过根据那些说“可以”的资料进行配置,却还是无法做到延迟加载。而把这个问题发到NHibernate的用户邮件列表中也没有得到答复。不管怎么样,我把普通的配置也发布在这里吧。

 
 
 
  1.  version="1.0" encoding="utf-8" ?>
  2.  xmlns="urn:nhibernate-mapping-2.2" assembly="NHTest" namespace="NHTest">
  3.    name="User" table="`User`">
  4.      name="UserID" type="Int32" column="UserID">
  5.        class="identity" />
  6.     
  7.      name="Detail" class="UserDetail" cascade="save-update" lazy="proxy" />
  8.      name="Name" type="String" />
  9.   
  10.    name="UserDetail" table="`UserDetail`" lazy="true">
  11.      name="UserID" type="Int32" column="UserID">
  12.        class="foreign">
  13.          name="property">User
  14.       
  15.     
  16.      name="User" class="User" constrained="true" />
  17.      name="Age" type="Int32" />
  18.   

按照某些资料的说法,我们把one-to-one的lazy设为proxy,并且把UserDetail节点的lazy设为true,便可以实现延迟加载。也就是说,在执行以下代码时,并不会去获取UserDetail的内容:

var user = session.Get(1);
可是现在,NHibernate告诉我们现在使用的SQL是这样子的(您也可以使用SQL Profiler进行观察):

 
 
 
  1. SELECT
  2.     user0_.UserID as UserID0_1_,
  3.     user0_.Name as Name0_1_,
  4.     userdetail1_.UserID as UserID1_0_,
  5.     userdetail1_.Age as Age1_0_
  6. FROM
  7.     [User] user0_
  8. left outer join
  9.     [UserDetail] userdetail1_
  10.         on user0_.UserID=userdetail1_.UserID
  11. WHERE
  12.     user0_.UserID=@p0;
  13. @p0 = 1

很明显,它仍然把UserDetail一并获取出来了。如果您觉得这里哪里错了,请告诉我。

开始绕弯路

从现在开始,我们就要走“弯路”了。虽然我们无法在一对一映射的情况下实现延迟加载,但是我们可以轻易做到“一对多”映射时,延迟加载“集合”中的子对象。我们这个workaround的关键,便是利用了“一对多”情况下的延迟加载,把“一对一”作为“一对多”的特殊情况进行处理。不过这里就需要我们修改User的Model了:

 
 
 
  1. public class User
  2. {
  3.     public virtual int UserID { get; set; }
  4.     public virtual string Name { get; set; }
  5.     private ISet m_detailLazyProxySet;
  6.     private ISet DetailLazyProxySet
  7.     {
  8.         get
  9.         {
  10.             if (this.m_detailLazyProxySet == null)
  11.             {
  12.                 this.m_detailLazyProxySet = new HashedSet();
  13.             }
  14.             return this.m_detailLazyProxySet;
  15.         }
  16.         set
  17.         {
  18.             this.m_detailLazyProxySet = value;
  19.         }
  20.     }
  21.     public virtual UserDetail Detail
  22.     {
  23.         get
  24.         {
  25.             return this.DetailLazyProxySet.Count <= 0 ? null :
  26.                 this.DetailLazyProxySet.Single();
  27.         }
  28.         set
  29.         {
  30.             this.DetailLazyProxySet.Clear();
  31.             this.DetailLazyProxySet.Add(value);
  32.         }
  33.     }
  34. }

也多亏NHibernate支持对private属性的读写,我们可以把DetailLazyProxySet设为私有属性,对外部保持“纯洁”——但是,很明显我们还是污染了Model。因此,这无论如何也只是一个workaround。

如果您使用xml进行配置,这自然没有什么问题。不过我还是喜欢使用Fluent NHibernate,流畅,方便,还可以导出为xml。因此,我们这里提供Fluent NHibernate的代码,相信您也可以轻易得出它所对应的xml配置内容:

 
 
 
  1. public class UserMap : ClassMap
  2. {
  3.     public UserMap()
  4.     {
  5.         Id(u => u.UserID).GeneratedBy.Identity();
  6.         Map(u => u.Name);
  7.         var paramExpr = Expression.Parameter(typeof(User));
  8.         var propertyExpr = Expression.Property(paramExpr, "DetailLazyProxySet");
  9.         var castExpr = Expression.Convert(propertyExpr, typeof(IEnumerable));
  10.         var lambdaExpr = Expression.Lambda, IEnumerable>>(castExpr, paramExpr);
  11.         HasMany(lambdaExpr)
  12.             .LazyLoad()
  13.             .AsSet()
  14.             .KeyColumnNames.Add("UserID")
  15.             .Cascade.All()
  16.             .Inverse();
  17.     }
  18. }
  19. public class UserDetailMap : ClassMap
  20. {
  21.     public UserDetailMap()
  22.     {
  23.         Id(d => d.UserID).GeneratedBy.Foreign("User");
  24.         Map(d => d.Age);
  25.         HasOne(d => d.User).Constrained();
  26.     }
  27. }

值得一提的是,由于DetailLazyProxySet是私有的,我们必须手动地构造一个Lambda表达式传递给HasMany方法。在实际使用过程中,我们应该提供额外的辅助方法(自然是为ClassMap新增一个扩展方法),并配合约定(属性名 + LazyProxySet)来进行强类型的编码定义。它可能是这样的:

HasOneByProxySet(u => u.Detail)...
嗯,就是这么点代码。

实验

打开NHibernate的SQL输出,并编写如下代码:

 
 
 
  1. var user = session.Get(1);
  2. Console.WriteLine("Name: {0}", user.Name);
  3. Console.WriteLine("Age: {0}", user.Detail.Age);

输出如下:

 
 
 
  1. NHibernate:
  2.     SELECT
  3.         user0_.UserID as UserID1_0_,
  4.         user0_.Name as Name1_0_
  5.     FROM
  6.         [User] user0_
  7.     WHERE
  8.         user0_.UserID=@p0;
  9.     @p0 = 1
  10. ===> Name: Jeffrey Zhao
  11. NHibernate:
  12.     SELECT
  13.         detaillazy0_.UserID as UserID1_,
  14.         detaillazy0_.UserID as UserID0_0_,
  15.         detaillazy0_.Age as Age0_0_
  16.     FROM
  17.         [UserDetail] detaillazy0_
  18.     WHERE
  19.         detaillazy0_.UserID=@p0;
  20.     @p0 = 1
  21. ===> Age: 25

请注意两条输出(已标红)的位置,很明显现在已经实现了延迟加载。那么我们要“饥渴加载(Eager Load)”又当如何?其实也很简单:

 
 
 
  1. var user = session
  2.     .CreateCriteria()
  3.     .SetFetchMode("DetailLazyProxySet", FetchMode.Eager)
  4.     .Add(Expression.IdEq(8))
  5.     .UniqueResult();
  6. Console.WriteLine("===> Name: {0}", user.Name);
  7. Console.WriteLine("===> Age: {0}", user.Detail.Age);

同样,“扩展方法”配合“约定”,我们可以把SetFetchMode这行古怪的代码改成:

.SetFetchMode(u => u.Detail)...

输出如下:

 
 
 
  1. NHibernate:
  2.     SELECT
  3.         this_.UserID as UserID1_1_,
  4.         this_.Name as Name1_1_,
  5.         detaillazy2_.UserID as UserID3_,
  6.         detaillazy2_.UserID as UserID0_0_,
  7.         detaillazy2_.Age as Age0_0_
  8.     FROM
  9.         [User] this_
  10.     left outer join
  11.         [UserDetail] detaillazy2_
  12.             on this_.UserID=detaillazy2_.UserID
  13.     WHERE
  14.         this_.UserID = @p0;
  15.     @p0 = 8
  16. ===> Name: Jeffrey Zhao
  17. ===> Age: 25

我们的饥渴换来数据库的级联,和谐而统一。

NHibernate一对一映射的延迟加载总结

至此,我们成功地实现了“一对一”的延迟加载,但是对NHibernate来说,一切都是个一对多的关系。我们获得了表面的成功,付出了“Model被污染”的代价。

原文来自赵劼的博客园博文《NHibernate中一对一关联的延迟加载》

网站题目:浅析NHibernate一对一映射的延迟加载
网页路径:http://www.shufengxianlan.com/qtweb/news31/293481.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联