我有这样的设置:父,有一个Child的集合。
class Parent {
IList<Child> Childs { get; set; }
}
HQL:
(“From Parent”)。Future();
(“From Child”)。Future();
foreach(Parent p in result) {
foreach(Child c in p.Childs) {
}
}
这给出了经典的N + 1问题。两个SQL语句在1次往返中发送到服务器,因此所有数据都存在于一级缓存中,那么为什么NH仍然存在每个孩子的SQL。
版本3.1.0.400
答案 0 :(得分:5)
执行将来的查询时,将所有Parent和Child对象拉入第一级缓存。 Parent对象包含一个需要填充的惰性集合。要填充集合,NHibernate必须查询数据库。 (我们将在一秒钟内了解原因。)查询返回Child对象,这些子对象已经在L1缓存中。因此,这些对象用于填充集合。
现在为什么NHibernate必须查询数据库以填充Childs集合?您可以在集合上使用“where”子句过滤掉IsDeleted == true的Child对象。您可以在EventListener中使用过滤掉某些Child对象的代码。基本上可以发生很多事情,NHibernate不能对Parent和Child对象之间的关系做出任何假设。
您可以通过在HQL或映射中指定提取策略来为其提供足够的信息。在HQL中,您可以写:
var parents = session.CreateQuery("from Parent p join fetch p.Childs").Future<Parent>();
使用未来的Child对象查询将完全是可选的,因为您正在使用父项获取子项。由于连接提取,您将获得重复的父对象,尽管它们将是同一个对象。 (您正在数据库中进行内部联接,并为每个子行返回父行的一个副本。)您可以通过迭代parent.Distinct()来摆脱这些。
如果您总是想要使用相应的Parent获取Child对象,您还可以在Parent映射中使用fetch =“join”。
<bag name="Children" cascade="all-delete-orphan" fetch="join">
<key column="ParentId"/>
<one-to-many class="Child"/>
</bag>
如果这些选项都不适用于您的方案,则可以在集合映射上指定批量大小。当你点击parent.Childs时,你仍然会执行数据库查询,但NHibernate会急切地初始化任何其他集合代理。
<bag name="Children" cascade="all-delete-orphan" batch-size="10">
<key column="ParentId"/>
<one-to-many class="Child"/>
</bag>