NHibernate中一对一关联的延迟加载

由于项目需要,我最近对.NET平台下各ORM框架(LINQ to SQL、Entity Framework V2 & V4、以及NHibernate)进行了功能对比,NHiberante可以说是各框架之中历史最为悠久,功能最强,也是使用最为复杂的一个。在使用NHibernate的过程中也遇到了许多麻烦,不过也得到了不少体会。例如NH的不足之处,我理想中的ORM框架是怎么样的,等等这些,以后有机会也可以慢慢和各位进行讨论。

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

  问题描述

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

  一对一映射

  主键关联

  主表的ID为自增字段

  所有字段NOT NULL。

  主表和子表设置级联删除

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

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


public class User 
{ 
  public virtual int UserID { get; set; } 
  public virtual string Name { get; set; } 
  public virtual UserDetail Detail { get; set; } 
} 
 
public class UserDetail 
{ 
  public virtual int UserID { get; set; } 
  public virtual int Age { get; set; } 
  public virtual User User { get; set; } 
}

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

CREATE TABLE [dbo].[User]( 
  [UserID] [int] IDENTITY(1,1) NOT NULL, 
  [Name] [nvarchar](50) NOT NULL, 
 CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED 
( 
  [UserID] ASC 
)) 
GO 
 
CREATE TABLE [dbo].[UserDetail]( 
  [UserID] [int] NOT NULL, 
  [Age] [int] NOT NULL, 
 CONSTRAINT [PK_UserDetail] PRIMARY KEY CLUSTERED 
( 
  [UserID] ASC 
)) 
GO 
 
ALTER TABLE [dbo].[UserDetail] WITH CHECK ADD 
CONSTRAINT [FK_UserDetail_User] FOREIGN KEY([UserID]) 
REFERENCES [dbo].[User] ([UserID]) 
ON DELETE CASCADE 
GO 
ALTER TABLE [dbo].[UserDetail] CHECK CONSTRAINT [FK_UserDetail_User] 
GO 

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

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

  传统一对一映射

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


<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHTest" namespace="NHTest"> 
 <class name="User" table="`User`"> 
  <id name="UserID" type="Int32" column="UserID"> 
   <generator class="identity" /> 
  </id> 
  <one-to-one name="Detail" class="UserDetail" cascade="save-update" lazy="proxy" /> 
  <property name="Name" type="String" /> 
 </class> 
 
 <class name="UserDetail" table="`UserDetail`" lazy="true"> 
  <id name="UserID" type="Int32" column="UserID"> 
   <generator class="foreign"> 
    <param name="property">User</param> 
   </generator> 
  </id> 
  <one-to-one name="User" class="User" constrained="true" /> 
  <property name="Age" type="Int32" /> 
 </class> 
</hibernate-mapping> 

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

可是现在,NHibernate告诉我们现在使用的SQL是这样子的(您也可以使用SQL Profiler进行观察):
SELECT 
  user0_.UserID as UserID0_1_, 
  user0_.Name as Name0_1_, 
  userdetail1_.UserID as UserID1_0_, 
  userdetail1_.Age as Age1_0_ 
FROM 
  [User] user0_ 
left outer join 
  [UserDetail] userdetail1_ 
    on user0_.UserID=userdetail1_.UserID 
WHERE 
  user0_.UserID=@p0; 
@p0 = 1

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

  开始绕弯路

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

情况进行处理。不过这里就需要我们修改User的Model了:

public class User 
{ 
  public virtual int UserID { get; set; } 
  public virtual string Name { get; set; } 
 
  private ISet<UserDetail> m_detailLazyProxySet; 
  private ISet<UserDetail> DetailLazyProxySet 
  { 
    get 
    { 
      if (this.m_detailLazyProxySet == null) 
      { 
        this.m_detailLazyProxySet = new HashedSet<UserDetail>(); 
      } 
 
      return this.m_detailLazyProxySet; 
    } 
    set 
    { 
      this.m_detailLazyProxySet = value; 
    } 
  } 
 
  public virtual UserDetail Detail 
  { 
    get 
    { 
      return this.DetailLazyProxySet.Count <= 0 ? null : 
        this.DetailLazyProxySet.Single(); 
    } 
    set 
    { 
      this.DetailLazyProxySet.Clear(); 
      this.DetailLazyProxySet.Add(value); 
    } 
  } 
}

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

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


public class UserMap : ClassMap<User> 
{ 
  public UserMap() 
  { 
    Id(u => u.UserID).GeneratedBy.Identity(); 
    Map(u => u.Name); 
 
    var paramExpr = Expression.Parameter(typeof(User)); 
    var propertyExpr = Expression.Property(paramExpr, "DetailLazyProxySet"); 
    var castExpr = Expression.Convert(propertyExpr, typeof(IEnumerable<UserDetail>)); 
    var lambdaExpr = Expression.Lambda<Func<User, IEnumerable<UserDetail>>>(castExpr, paramExpr); 
    HasMany(lambdaExpr) 
      .LazyLoad() 
      .AsSet() 
      .KeyColumnNames.Add("UserID") 
      .Cascade.All() 
      .Inverse(); 
  } 
} 
 
public class UserDetailMap : ClassMap<UserDetail> 
{ 
  public UserDetailMap() 
  { 
    Id(d => d.UserID).GeneratedBy.Foreign("User"); 
    Map(d => d.Age); 
    HasOne(d => d.User).Constrained(); 
  } 
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值