如何使用 NHibernate 加载大型复杂对象图

本文关键字:复杂 对象图 大型 加载 何使用 NHibernate | 更新日期: 2023-09-27 17:57:10

给定一个对象图,如下所示:

A { IEnum<B> }
B { IEnum<C>, IEnum<D>, IEnum<E>, ... }
C { IEnum<X> }

如何急切地加载整个对象图而不会出现 N+1 问题?

以下是我最终想要执行的查询的伪代码:

var a = Session.Get<A>(1); // Query 1
var b_Ids = foreach(b in A.B's) => Select(b.Id); // Query 2
var c = Session.CreateQuery("from C where B in (b_Ids)").Future<C>(); // Query 3
var d = Session.CreateQuery("from D where B in (b_Ids)").Future<D>(); // Query 3
var e = Session.CreateQuery("from E where B in (b_Ids)").Future<E>(); // Query 3
// Iterate through c, d, e, ... find the correct 'B' parent, add to collection manually

我对这种方法遇到的问题是,当我将"C"、"D"和"E"的实例添加到父"B"的相应集合中时,该集合仍然是代理的,并且当.调用 Add(),代理初始化自身并执行更多查询;我认为 NHibernate 无法看到我已经在第一级缓存中拥有所有数据,这是可以理解的。

我试图通过在我的 Add 方法中执行以下操作来解决此问题:

void Add(IEnum<C>)
{
    _collection = new Collection<C>(); // replace the proxied instance to prevent initialization
    foreach(c) => _collection.Add(c);
}

这给了我想要的最佳查询策略,但后来在执行持久性时赶上了我(NHibernate从我能知道的某个地方跟踪原始集合by-ref)。

所以我的问题是,我如何加载一个复杂的图,其中包含没有 N+1 的孩子的孩子?到目前为止,我唯一遇到的是加入B-C,B-D,B-E,这在我的情况下是不可接受的。

我们正在使用带有FluentHN的NH 2.1.2进行映射。升级到 NH 的 v3 或使用 hbm/存储的过程/任何东西都不会被排除在外。

更新:其中一条评论提到了一种联接方法,我确实遇到了一个演示这种方法的博客。在我们的情况下,这种解决方法是不可接受的,但它可能会帮助其他人: 急于使用 NHibernate 在 1 次往返中获取多个子集合

更新 2:乔丹的回答让我看到了以下与我的问题相关的帖子:类似问题和Ayende的博客。此时悬而未决的问题是"如何在没有每条路径往返的情况下执行子选择"。

更新 3:我已经接受了乔丹的答案,即使子选择解决方案不是最佳的。

如何使用 NHibernate 加载大型复杂对象图

您可以使用可以在映射文件中设置的 SubSelect 获取。 这将避免 N+1 和笛卡尔积。

首先,您可以更改映射以急切地加载这些集合。 请参阅本节中的项目 #4。
其次 - 我相信您的集合似乎加载了两次的原因是您首先使用查询获取它,然后使用集合属性。
意思 - nHibernate区分用户生成的查询(如您使用的查询)和它自己生成的查询(如您第一次阅读"C"集合时发生的查询)。它们不混合。
因此,当您第一次读取"C"集合时,nHib 无法识别它实际上曾经向数据库发送过完全相同的查询(因为它是用户查询),并再次发送它。
避免这种情况的方法是通过 B 实体检索 C 集合。