zoukankan      html  css  js  c++  java
  • NHibernate教程(12)--延迟加载

    本节内容

    • 引入
    • 延迟加载
    • 实例分析
      • 1.一对多关系实例
      • 2.多对多关系实例
    • 结语

    引入

    通过前面文章的分析,我们知道了如何使用NHibernate,比如CRUD操作、事务、一对多、多对多映射等问题,这篇我们初步探索NHibernate中的加载机制。

    在讨论之前,我们看看我们使用的数据模型,回顾一下第二篇建立的数据模型。

    数据关系模型

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

    延迟加载(Lazy Loading)

    延迟加载按我现在的理解应该叫“视需要加载(load-on-demand)”,“(delayed  loading)”,“刚好及时加载(just-in-time loading)”在合适不过了。这里按字面理解延迟仿佛变成了“延迟,延长,拖延时间”的意思。

    NHibernate从1.2版本就默认支持了延迟加载。其实延迟加载的执行方式是使用GoF23中的代理模式,我们用一张图片来大致展示延迟加载机制。

    延迟加载机制-代理模式

    实例分析

    1.一对多关系实例

    在一对多关系实例中,我们使用Customer对象与Order对象为例,在数据访问层中编写两个方法用于在测试时调用,分别是:

    数据访问层中方法一:加载Customer对象

    public Customer LazyLoad(int customerId)
    {
        return _session.Get<Customer>(customerId);
    }

    数据访问层中方法二:加载Customer对象并使用Using强制清理关闭Session

    public Customer LazyLoadUsingSession(int customerId)
    {
        using (ISession _session = new SessionManager().GetSession())
        {
            return _session.Get<Customer>(customerId);
        }
    }

    1.默认延迟加载

    调用数据访问层中的LazyLoad方法加载一个Customer对象,NHibernate的默认延迟加载Customer关联的Order对象。利用NHibernate提供有用类(NHibernateUtil)测试被关联的Customer对象集合是否已初始化(也就是已加载)。

    [Test]
    public void LazyLoadTest()
    {
        Customer customer = _relation.LazyLoad(1);
        Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Orders));
    }

    测试成功,观察NHibernate生成SQL语句为一条查询Customer对象的语句。我们使用调试发现,Orders对象集合的属性值为:{Iesi.Collections.Generic.HashedSet`1[DomainModel.Entities.Order]},并可以同时看到Order对象集合中的项。截图如下:

    验证延迟加载

    2.延迟加载并关闭Session

    同第一个测试相同,这个测试调用使用Using强制资源清理Session加载Customer对象的方法。

    [Test]
    public void LazyLoadUsingSessionTest()
    {
        Customer customer = _relation.LazyLoadUsingSession(1);
        Assert.IsFalse(NHibernateUtil.IsInitialized(customer.Orders));
    }

    测试成功,其生成SQL语句和上面测试生成SQL语句相同。但是使用调试发现,Orders对象集合的属性值为:NHibernate.Collection.Generic.PersistentGenericSet<DomainModel.Entities.Order> ,如果你进一步想看看Order对象集合中的项,它抛出了HibernateException异常:failed  to lazily initialize a collection, no session or session was closed。截图如下:

    一对多关系延迟加载异常

    2.多对多关系实例

    同理,在多对多关系实例中,我们以Order对象与Products对象为例,我们在数据访问层中写两个方法用于测试:

    方法1:加载Order对象

    public DomainModel.Entities.Order LazyLoadOrderAggregate(int orderId)
    {
        return _session.Get<DomainModel.Entities.Order>(orderId);
    }

    方法2:加载Customer对象并使用Using强制清理关闭Session

    public DomainModel.Entities.Order LazyLoadOrderAggregateUsingSession(int orderId)
    {
        using (ISession _session = new SessionManager().GetSession())
        {
            return _session.Get<DomainModel.Entities.Order>(orderId);
        }
    }

    1.默认延迟加载

    调用数据访问层中的LazyLoadOrderAggregate方法加载一个Order对象,NHibernate的默认延迟加载Order关联的Products对象集合(多对多关系),利用代理模式加载Customer对象集合(多对一关系)。利用NHibernate提供有用类(NHibernateUtil)测试被关联的Products对象集合和Customer对象集合是否已初始化。

    [Test]
    public void LazyLoadOrderAggregateTest()
    {
        Order order = _relation.LazyLoadOrderAggregate(2);
        Assert.IsFalse(NHibernateUtil.IsInitialized(order.Customer));
        Assert.IsFalse(NHibernateUtil.IsInitialized(order.Products));
    }

    测试成功,NHibernate生成SQL语句如下:

    SELECT order0_.OrderId as OrderId1_0_,
           order0_.Version as Version1_0_,
           order0_.OrderDate as OrderDate1_0_,
           order0_.Customer as Customer1_0_
    FROM [Order] order0_ WHERE order0_.OrderId=@p0; @p0 = '2'

    调试看看效果截图,可以清楚的观察到Customer对象和Products对象集合的类型。

    多对多关系延迟加载

    2.延迟加载并关闭Session

    同第一个测试相同,这个测试调用使用Using强制资源清理Session加载Order对象的方法。

    [Test]
    public void LazyLoadOrderAggregateUsingSessionTest()
    {
        Order order = _relation.LazyLoadOrderAggregateUsingSession(2);
        Assert.IsFalse(NHibernateUtil.IsInitialized(order.Customer));
        Assert.IsFalse(NHibernateUtil.IsInitialized(order.Products));
    }

    测试成功,其生成SQL语句和上面测试生成SQL语句相同。但是使用调试发现,Customer对象类型为:{CustomerProxy9dfb54eca50247f69bfedd92e1638ba5},进一步观察Customer对象Firstname、Lastname等项引发了“NHibernate.LazyInitializationException”类型的异常。Products对象集合的属性值为:{NHibernate.Collection.Generic.PersistentGenericBag<DomainModel.Entities.Product>},如果你进一步想看看Products对象集合中的项它同样抛出HibernateException异常:failed  to lazily initialize a collection, no session or session was  closed。下面截取获取Customer对象一部分图,你想知道全部自己亲自调试一把:

    多对多关系延迟加载并关闭Session调试

    3.延迟加载中LazyInitializationException异常

    上面测试已经说明了这个问题:如果我想在Session清理关闭之后访问Order对象中的某些项会得到一个异常,由于session关闭,NHibernate不能为我们延迟加载Order项,我们编写一个测试方法验证一下:

    [Test]
    [ExpectedException(typeof(LazyInitializationException))]
    public void LazyLoadOrderAggregateUsingSessionOnFailTest()
    {
        Order order = _relation.LazyLoadOrderAggregateUsingSession(2);
        string name = order.Customer.Name.Fullname;
    }

    上面的测试抛出“Could not initialize proxy - no Session”预计的LazyInitializationException异常,表明测试成功,证明不能加载Order对象的Customer对象。

    4.N+1选择问题

    我们在加载Order后访问Product项,导致访问Product每项就会产生一个选择语句,我们用一个测试方法来模拟这种情况:

    [Test]
    public void LazyLoadOrderAggregateSelectBehaviorTest()
    {
        Order order = _relation.LazyLoadOrderAggregate(2);
        float sum = 0.0F;
        foreach (var item in order.Products)
        {
            sum += item.Cost;
        }
        Assert.AreEqual(21.0F, sum);
    }

    NHibernate生成SQL语句如下:

    SELECT order0_.OrderId as OrderId1_0_,
           order0_.Version as Version1_0_,
           order0_.OrderDate as OrderDate1_0_,
           order0_.Customer as Customer1_0_ 
    FROM [Order] order0_ WHERE order0_.OrderId=@p0; @p0 = '2'
    
    SELECT products0_.[Order] as Order1_1_, 
           products0_.Product as Product1_,
           product1_.ProductId as ProductId3_0_,
           product1_.Version as Version3_0_,
           product1_.Name as Name3_0_,
           product1_.Cost as Cost3_0_ 
    FROM OrderProduct products0_ 
    left outer join Product product1_ on 
    products0_.Product=product1_.ProductId 
    WHERE products0_.[Order]=@p0; @p0 = '2'

    这次我走运了,NHibernate自动生成最优化的查询语句,一口气加载了两个Product对象。但是试想一下有一个集合对象有100项,而你仅仅需要访问其中的一两项。这样加载所有项显然是资源的浪费。

    幸好,NHibernate为这些问题有一个方案,它就是立即加载。欲知事后如何,请听下回分解!

    结语

    这篇我们初步认识了NHibernate中的加载机制,这篇从一对多关系、多对多关系角度分析了NHibernate默认加载行为——延迟加载,下篇继续分析立即加载。希望对你有所帮助

  • 相关阅读:
    Constants and Variables
    随想
    C#基础篇之语言和框架介绍
    Python基础19 实例方法 类方法 静态方法 私有变量 私有方法 属性
    Python基础18 实例变量 类变量 构造方法
    Python基础17 嵌套函数 函数类型和Lambda表达式 三大基础函数 filter() map() reduce()
    Python基础16 函数返回值 作用区域 生成器
    Python基础11 List插入,删除,替换和其他常用方法 insert() remove() pop() reverse() copy() clear() index() count()
    Python基础15 函数的定义 使用关键字参数调用 参数默认值 可变参数
    Python基础14 字典的创建修改访问和遍历 popitem() keys() values() items()
  • 原文地址:https://www.cnblogs.com/zhengwei-cq/p/7381183.html
Copyright © 2011-2022 走看看