在 NHibernate 3.0 Linq 中急切加载多个兄弟姐妹和孙辈(堂兄弟?)的良好行为

Posted

技术标签:

【中文标题】在 NHibernate 3.0 Linq 中急切加载多个兄弟姐妹和孙辈(堂兄弟?)的良好行为【英文标题】:Good behaviour for eager loading multiple siblings and grandchildren (cousins?) in NHibernate 3.0 Linq 【发布时间】:2011-02-28 14:49:57 【问题描述】:

我正在尝试使用 NHibernate 3.0 的 LINQ 接口执行以下操作。我想查询一个对象(使用一些 Where 子句),并加载一些子孙。目前我正在这样做:

var results = session.Query<Thing>()
                     .Where(...)
                     .Fetch(x => x.SubThingA)
                     .ThenFetch(st => st.SubSubThingA)

                     .Fetch(x => x.SubThingB)
                     .ThenFetch(st => st.SubSubThingB)

                     // etc...

但是,这会导致所有孙子之间的笛卡尔积(每个结果行都包含很多很多列)。这由“ayende”here 讨论。另一方面,我得到了一次往返,这与拆分查询然后组合它不同。

我怎样才能以更好的方式(SQL 和性能方面),仍然使用 NHibernate 的 LINQ 接口?

(一方面,我注意到当前 ToFuture 方法在您使用 Fetch 时不起作用)

非常感谢!

【问题讨论】:

一个相关问题:有没有办法使用 LINQ 在一次往返中执行多个查询? (鉴于 Future 似乎不适用于使用 Fetch 的 LINQ 查询) 我们在这里谈论多少个孩子?将完整的对象图发送到远程客户端(我也在这样做)在性能方面比访问数据库两次或三次而不是一次更昂贵。在这种情况下,我通常拆分查询。此外,您应该考虑是否从一开始就需要整个对象图,或者是否还可以在需要时动态加载其他一些子对象。 Florian Lim,这个对象有很多很多的孩子。我也宁愿访问数据库而不是客户端,但我试图避免这种情况并在单个查询中执行整个操作。 Afaik 它不能用 NHibernate Linq 3.0 完成,但我会关注这个问题,如果你得到一个有效的答案,我会很乐意使用它。 (+1) 【参考方案1】:

在大多数情况下,通过在实体和集合中使用 batch-size 而不是创建大型查询,您可以获得更好的性能。

最好的情况,它是根据每个根实体类型的 id 进行查询。


假设您有一个根实体 Customer,它有一个 Orders 集合,它有一个 OrderItems 集合,它引用 Products,所有batch-size 属性都设置为1000

假设您检索了一个包含 10 个客户的列表,这些客户平均有 10 个订单,每个订单有 10 个产品:

var results = session.Query<Customer>().Where(...).Take(10).ToList();
第一个查询将只获取客户。 当您开始迭代第一个 customer.Orders 集合时,将使用一个查询来加载所有这些集合(针对所有客户) 当您开始迭代第一个 order.OrderItems 集合时,将使用一个查询来加载所有这些集合(针对所有订单和所有客户) 当您从第一个产品中读取属性时,将使用一个查询来加载所有属性

所以,您只有 4 个查询,根本没有连接,通过 PK 检索所有内容。既简单又高效。

【讨论】:

感谢您的回复。我的情况是我需要加载所有必需的对象图(它正在发送到远程客户端)。从我收集的信息来看,当您维护一个打开的会话并使用惰性界面时,批量大小是好的。从一开始就需要大图时,最好的解决方案是什么? @sinelaw:根据您的评论,我了解到您正在尝试“按原样”序列化对象图。不。你只是在人为地限制自己。 Diego - “人为地限制自己”是什么意思?你能解释一下你指的是什么问题吗? @sinelaw:如果您使用显式合约序列化图表,您就可以让 NH 发挥它的魔力。 Diego - 好的,明白了,听起来确实是个好主意。【参考方案2】:

虽然 Diego 的回答是在 NHibernate 中做这些事情的公认方法,但我真的对这种方法感到不舒服。我不想仅仅因为我可能需要以某些方式检索它们,就必须为我的对象定义显式契约。此外,我并不总是想要序列化它们。此外,在许多情况下,我知道最好的性能始终是一次往返以获取所有数据。

我最终使用的解决方案是实现一个函数,该函数采用根对象上的(类型安全)表达式列表,例如

x => x.Child.GrandChild1
x => x.Child.GrandChild2Collection.SubInclude(c => c.GreatGrandChild)

其中 SubInclude 是解析这些表达式时使用的 IEnumerable 的扩展方法。

我解析这个表达式列表并为每个表达式的每个子路径 (x, x.Child, x.Child.GrandChild1) 构建一个基于根类型的 NHibernate 标准查询:

var queryOver = session.QueryOver<T>().Where( ...expression to select root objects... );
for every subpath in the current expression:
    queryOver.RootCriteria.SetFetchMode(subPath, FetchMode.Eager)

queryOver.RootCriteria
         .SetResultTransformer(new DistinctRootEntityResultTransformer())

queryOver.Future()

对列表中的每个表达式都重复此操作。 最后一行确保这个急切的获取将包含在接下来发生的任何往返行程中。然后我对根对象 T 进行实际查询,会话在同一次往返中自动执行获取我在表达式中传递的每个路径所需的所有查询。

对每个表达式路径分别执行查询,因此不存在笛卡尔积问题。

底线是这绝非易事。我的代码太多了,无法按原样发布。我更喜欢 EF4.1 的 Include(expression) API,它可以自动完成所有这些操作。

【讨论】:

重要:我的实体类中的所有集合都定义为集合。否则,NHibernate 最终会返回孙子节点和更深节点的副本。

以上是关于在 NHibernate 3.0 Linq 中急切加载多个兄弟姐妹和孙辈(堂兄弟?)的良好行为的主要内容,如果未能解决你的问题,请参考以下文章

使用 NHibernate 3.0 QueryOver 或 LINQ 提供程序的权衡

在 NHibernate 中强制进行急切的选择

Fluent NHibernate:在映射中急切加载多个集合

Nhibernate:只读属性导致急切加载

NHibernate 急切负载

NHibernate 3.0:没有使用QueryOver的FirstOrDefault()?