找回密码
 立即注册
首页 业界区 业界 NHibernate之旅(12):初探延迟加载机制

NHibernate之旅(12):初探延迟加载机制

璋锌 2025-5-30 01:26:54
本节内容
           
  • 引入       
  • 延迟加载       
  • 实例分析
                     
    • 1.一对多关系实例               
    • 2.多对多关系实例       
                   
  • 结语
引入

通过前面文章的分析,我们知道了如何使用NHibernate,比如CRUD操作、事务、一对多、多对多映射等问题,这篇我们初步探索NHibernate中的加载机制。
在讨论之前,我们看看我们使用的数据模型,回顾一下第二篇建立的数据模型。
1.jpeg

Customer与Orders是一对多关系,Order与Product是多对多关系。这一篇还是使用这个模型,有关具体配置和映射参考本系列的文章。
延迟加载(Lazy Loading)

延迟加载按我现在的理解应该叫“视需要加载(load-on-demand)”,“(delayed loading)”,“刚好及时加载(just-in-time loading)”在合适不过了。这里按字面理解延迟仿佛变成了“延迟,延长,拖延时间”的意思。
NHibernate从1.2版本就默认支持了延迟加载。其实延迟加载的执行方式是使用GoF23中的代理模式,我们用一张图片来大致展示延迟加载机制。
2.jpeg

实例分析

1.一对多关系实例

在一对多关系实例中,我们使用Customer对象与Order对象为例,在数据访问层中编写两个方法用于在测试时调用,分别是:
数据访问层中方法一:加载Customer对象
  1. public Customer LazyLoad(int customerId)
  2. {
  3.     return _session.Get<Customer>(customerId);
  4. }
复制代码
数据访问层中方法二:加载Customer对象并使用Using强制清理关闭Session
  1. public Customer LazyLoadUsingSession(int customerId)
  2. {
  3.     using (ISession _session = new SessionManager().GetSession())
  4.     {
  5.         return _session.Get<Customer>(customerId);
  6.     }
  7. }
复制代码
1.默认延迟加载

调用数据访问层中的LazyLoad方法加载一个Customer对象,NHibernate的默认延迟加载Customer关联的Order对象。利用NHibernate提供有用类(NHibernateUtil)测试被关联的Customer对象集合是否已初始化(也就是已加载)。
  1. [Test]
  2. public void LazyLoadTest()
  3. {
  4.     Customer customer = _relation.LazyLoad(1);
  5.     Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Orders));
  6. }
复制代码
测试成功,观察NHibernate生成SQL语句为一条查询Customer对象的语句。我们使用调试发现,Orders对象集合的属性值为:{Iesi.Collections.Generic.HashedSet`1[DomainModel.Entities.Order]},并可以同时看到Order对象集合中的项。截图如下:
3.jpeg

2.延迟加载并关闭Session

同第一个测试相同,这个测试调用使用Using强制资源清理Session加载Customer对象的方法。
  1. [Test]
  2. public void LazyLoadUsingSessionTest()
  3. {
  4.     Customer customer = _relation.LazyLoadUsingSession(1);
  5.     Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Orders));
  6. }
复制代码
测试成功,其生成SQL语句和上面测试生成SQL语句相同。但是使用调试发现,Orders对象集合的属性值为:NHibernate.Collection.Generic.PersistentGenericSet ,如果你进一步想看看Order对象集合中的项,它抛出了HibernateException异常:failed to lazily initialize a collection, no session or session was closed。截图如下:
4.jpeg

2.多对多关系实例

同理,在多对多关系实例中,我们以Order对象与Products对象为例,我们在数据访问层中写两个方法用于测试:
方法1:加载Order对象
  1. public DomainModel.Entities.Order LazyLoadOrderAggregate(int orderId)
  2. {
  3.     return _session.Get<DomainModel.Entities.Order>(orderId);
  4. }
复制代码
方法2:加载Customer对象并使用Using强制清理关闭Session
  1. public DomainModel.Entities.Order LazyLoadOrderAggregateUsingSession(int orderId)
  2. {
  3.     using (ISession _session = new SessionManager().GetSession())
  4.     {
  5.         return _session.Get<DomainModel.Entities.Order>(orderId);
  6.     }
  7. }
复制代码
1.默认延迟加载

调用数据访问层中的LazyLoadOrderAggregate方法加载一个Order对象,NHibernate的默认延迟加载Order关联的Products对象集合(多对多关系),利用代理模式加载Customer对象集合(多对一关系)。利用NHibernate提供有用类(NHibernateUtil)测试被关联的Products对象集合和Customer对象集合是否已初始化。
  1. [Test]
  2. public void LazyLoadOrderAggregateTest()
  3. {
  4.     Order order = _relation.LazyLoadOrderAggregate(2);
  5.     Assert.IsFalse(NHibernateUtil.IsInitialized(order.Customer));
  6.     Assert.IsFalse(NHibernateUtil.IsInitialized(order.Products));
  7. }
复制代码
测试成功,NHibernate生成SQL语句如下:
  1. SELECT order0_.OrderId as OrderId1_0_,
  2.        order0_.Version as Version1_0_,
  3.        order0_.OrderDate as OrderDate1_0_,
  4.        order0_.Customer as Customer1_0_
  5. FROM [Order] order0_ WHERE order0_.OrderId=@p0; @p0 = '2'
复制代码
调试看看效果截图,可以清楚的观察到Customer对象和Products对象集合的类型。
5.jpeg

2.延迟加载并关闭Session

同第一个测试相同,这个测试调用使用Using强制资源清理Session加载Order对象的方法。
  1. [Test]
  2. public void LazyLoadOrderAggregateUsingSessionTest()
  3. {
  4.     Order order = _relation.LazyLoadOrderAggregateUsingSession(2);
  5.     Assert.IsFalse(NHibernateUtil.IsInitialized(order.Customer));
  6.     Assert.IsFalse(NHibernateUtil.IsInitialized(order.Products));
  7. }
复制代码
测试成功,其生成SQL语句和上面测试生成SQL语句相同。但是使用调试发现,Customer对象类型为:{CustomerProxy9dfb54eca50247f69bfedd92e1638ba5},进一步观察Customer对象Firstname、Lastname等项引发了“NHibernate.LazyInitializationException”类型的异常。Products对象集合的属性值为:{NHibernate.Collection.Generic.PersistentGenericBag},如果你进一步想看看Products对象集合中的项它同样抛出HibernateException异常:failed to lazily initialize a collection, no session or session was closed。下面截取获取Customer对象一部分图,你想知道全部自己亲自调试一把:
6.jpeg

3.延迟加载中LazyInitializationException异常

上面测试已经说明了这个问题:如果我想在Session清理关闭之后访问Order对象中的某些项会得到一个异常,由于session关闭,NHibernate不能为我们延迟加载Order项,我们编写一个测试方法验证一下:
  1. [Test]
  2. [ExpectedException(typeof(LazyInitializationException))]
  3. public void LazyLoadOrderAggregateUsingSessionOnFailTest()
  4. {
  5.     Order order = _relation.LazyLoadOrderAggregateUsingSession(2);
  6.     string name = order.Customer.Name.Fullname;
  7. }
复制代码
上面的测试抛出“Could not initialize proxy - no Session”预计的LazyInitializationException异常,表明测试成功,证明不能加载Order对象的Customer对象。
4.N+1选择问题

我们在加载Order后访问Product项,导致访问Product每项就会产生一个选择语句,我们用一个测试方法来模拟这种情况:
  1. [Test]
  2. public void LazyLoadOrderAggregateSelectBehaviorTest()
  3. {
  4.     Order order = _relation.LazyLoadOrderAggregate(2);
  5.     float sum = 0.0F;
  6.     foreach (var item in order.Products)
  7.     {
  8.         sum += item.Cost;
  9.     }
  10.     Assert.AreEqual(21.0F, sum);
  11. }
复制代码
NHibernate生成SQL语句如下:
  1. SELECT order0_.OrderId as OrderId1_0_,
  2.        order0_.Version as Version1_0_,
  3.        order0_.OrderDate as OrderDate1_0_,
  4.        order0_.Customer as Customer1_0_
  5. FROM [Order] order0_ WHERE order0_.OrderId=@p0; @p0 = '2'
  6. SELECT products0_.[Order] as Order1_1_,
  7.        products0_.Product as Product1_,
  8.        product1_.ProductId as ProductId3_0_,
  9.        product1_.Version as Version3_0_,
  10.        product1_.Name as Name3_0_,
  11.        product1_.Cost as Cost3_0_
  12. FROM OrderProduct products0_
  13. left outer join Product product1_ on
  14. products0_.Product=product1_.ProductId
  15. WHERE products0_.[Order]=@p0; @p0 = '2'
复制代码
这次我走运了,NHibernate自动生成最优化的查询语句,一口气加载了两个Product对象。但是试想一下有一个集合对象有100项,而你仅仅需要访问其中的一两项。这样加载所有项显然是资源的浪费。
幸好,NHibernate为这些问题有一个方案,它就是立即加载。欲知事后如何,请听下回分解!
结语

这篇我们初步认识了NHibernate中的加载机制,这篇从一对多关系、多对多关系角度分析了NHibernate默认加载行为——延迟加载,下篇继续分析立即加载。希望对你有所帮助。
本系列链接:NHibernate之旅系列文章导航
NHibernate Q&A
           
  • 欢迎加入NHibernate中文社区,一起讨论NHibernate知识!       
  • 请到NHibernate中文社区下载本系列相关源码。
下次继续分享NHibernate!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册