一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

NHibernate 2.1.2 延迟加载配置与验证教程

时间:2026-09-21 19:36:01 编辑:袖梨 来源:一聚教程网

NHibernate 2.1.2 的延迟加载通过代理对象和持久化集合实现:先在 .hbm.xml 中保持实体/关联的 lazy 设置,再让实体类及可延迟成员能被代理,最后在 ISession 开启时访问这些成员。若应当把数据带出 Session,应在关闭前显式初始化、采用 join fetch 一次取齐,或转成 DTO,不要把已脱离 Session 的代理留给视图层猜。

延迟加载的三个对象

  1. 实体代理session.Load<Order>(id) 可先返回只含标识的代理,访问普通属性时再查数据库。
  2. 单值关联:如 Order.Customer,通常通过代理延迟取对端实体。
  3. 集合:如 Order.Lines,先持有持久化集合包装,枚举、读取数量或执行其他需要内容的操作时再发 SQL。

NHibernate 官方 2.1 系列文档指出,默认获取计划会对集合采用 lazy select,对实体采用 lazy proxy。具体项目仍应以自己的映射文件为准,因为顶层 default-lazy 或局部 lazy="false" 会改变行为。

准备可代理的实体类

public class Order
{
    public virtual int Id { get; protected set; }
    public virtual DateTime CreatedAt { get; set; }
    public virtual Customer Customer { get; set; }
    public virtual ISet<OrderLine> Lines { get; protected set; }
}

在常见的子类代理方案中,类不能是 sealed,需延迟拦截的成员应为 virtual。实体的无参构造函数至少应让 NHibernate 能够调用。

在 hbm.xml 中配置延迟关联

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="Shop.Domain"
                   namespace="Shop.Domain">
  <class name="Order" table="Orders" lazy="true">
    <id name="Id" column="OrderId">
      <generator class="native" />
    </id>

    <property name="CreatedAt" not-null="true" />

    <many-to-one name="Customer"
                 class="Customer"
                 column="CustomerId"
                 lazy="proxy"
                 not-null="true" />

    <set name="Lines"
         table="OrderLines"
         inverse="true"
         lazy="true">
      <key column="OrderId" />
      <one-to-many class="OrderLine" />
    </set>
  </class>
</hibernate-mapping>

lazy="true" 使实体可通过代理延迟初始化;many-to-one lazy="proxy" 让单值关联使用代理;集合上的 lazy="true" 则让集合内容在需要时查询。映射必须在构建 SessionFactory 前加载。

用 SQL 日志验证载入时机

using (ISession session = sessionFactory.OpenSession())
{
    Order order = session.Load<Order>(42);

    // 通常只有标识时不需初始化代理
    Console.WriteLine(order.Id);

    // 访问普通属性后,代理需要加载 Order
    Console.WriteLine(order.CreatedAt);

    // 枚举集合时再加载 OrderLines
    foreach (OrderLine line in order.Lines)
        Console.WriteLine(line.Quantity);
}

验证时开启 NHibernate SQL 日志,关注查询是在哪一行访问之后出现。不要只凭断点中对象已经有值就判断延迟失效,调试器展开属性本身就可能触发加载。

数据必须离开 Session 时

方法一:在关闭前显式初始化

Order order;
using (ISession session = sessionFactory.OpenSession())
{
    order = session.Get<Order>(42);
    NHibernateUtil.Initialize(order.Customer);
    NHibernateUtil.Initialize(order.Lines);
}

这适合已知下一层确实应当这些关联的场景。不要对所有关联无条件初始化,否则只是把延迟加载改成了隐蔽的全量加载。

方法二:为本次用例使用 join fetch

Order order = session.CreateQuery(@"
        select distinct o
        from Order o
        left join fetch o.Lines
        where o.Id = :id")
    .SetInt32("id", 42)
    .UniqueResult<Order>();

join fetch 是查询级获取计划,不用为了某个页面就把全局映射改成 lazy="false"。对多个集合同时 join fetch 可能造成结果集笛卡尔放大,应按真实用例分解查询。

方法三:在 Session 内投影为 DTO

列表页只需要订单时间和行数时,直接查询所需列并返回 DTO,比将完整实体图交给已脱离 Session 的界面层更清楚。

LazyInitializationException 怎么查

  1. 确认异常中是哪个代理或集合未初始化。
  2. 确认访问发生时 ISession 是否已关闭或清理。
  3. 如果该数据是用例必需,在业务边界内用显式初始化、join fetch 或 DTO 取得。
  4. 如果不是必需,删除对该导航属性的隐式访问,特别是序列化器、日志和视图模板的自动属性遍历。
  5. 修改后重新检查 SQL 数量,避免从一个异常变成 N+1 查询。

总结

NHibernate 2.1.2 延迟加载要同时满足映射、可代理类设计和 Session 生命周期三个条件。正确使用时,默认保持 lazy,再由每个业务用例明确决定哪些关联需在 Session 内初始化,并用 SQL 日志确认没有引入 N+1。

热门栏目