急切地使用nHibernate加载树重新查询叶节点

时间:2011-10-08 00:10:35

标签: nhibernate orm tree nhibernate-mapping eager-loading

在宣布NHibernate框架被破坏或我自己疯狂之前寻找一些专业知识!

我正在尝试用NHibernate急切加载自引用树,并且可以成功加载大多数子列表,但是我似乎无法使用叶子节点。我的疑问是:

"select p from Proposal p join fetch p.Structures s join fetch s.theChildrenList c " +
"where p._persistenceId = :p1 and s.theProposal = :p1 and c.theProposal = :p1")
.SetParameter("p1", aProposalId)
.SetResultTransformer(new DistinctRootEntityResultTransformer()).
UniqueResult<Proposal>();

这将正确返回所有记录,但不能正确填充叶节点的childrenList(根据定义应为空)。相反,当我获得代理并搜索树时,成千上万的无记录零记录查询。

我试过了: 1.使用左连接而不是连接 2.让孩子的列表不懒和fetch =“join”并删除hql(作为概念证明,性能下降在其他地方是不可接受的) 3.弄乱我看到有人在hibernate论坛中使用的未找到的属性

所有这些都给了我相同的结果......一个包含所有数据的大型查询(好)和数千个不返回数据的小查询(错误)。有什么想法吗?

我正在使用NHibernate 2.2,这里是映射文件的相关部分供参考:

  <many-to-one    name="theParentStructure"
                  column="PARENT_STRUCTURE_ID"
                  class="Structure"
                  access="field"
                  update="false"
                  insert="false"
                  not-found="ignore"/>

  <bag            name="theChildrenList"
                  generic="true"
                  table="STRUCTURE"
                  access="field"
                  cascade="all-delete-orphan"
                  inverse="true" fetch="join" lazy="false">  <--Both with and without the last two properties
     <key column="PARENT_STRUCTURE_ID" />
     <one-to-many class="Structure"/>
  </bag>

任何帮助都将不胜感激!!

2 个答案:

答案 0 :(得分:2)

我很久以前就对这个话题很感兴趣,所以请不要把我认为理所当然的事情当作理所当然的事(如果我错了,也许有更多NHibernate经验的人可以纠正我。

上次我检查了描述的情况是一个很大的问题,因为Nhibernate正在为每个level个子实体生成单独的查询。如果你事先知道你的树的深度不会太大,那么你可能没有eager loading孩子的生活...但如果你的结构没有深度限制,你应该考虑替代方案

假设您使用的是SqlServer

我发现很容易实现的一个解决方案是使用recursive self-join / CTE。此解决方案涉及切换到stored-procedures作为查询源并在代码中手动重新创建层次结构。要获取整个树,您只需要一个数据库查询(可以包含各种过滤器和子查询+排序),您仍然可以对所有NHibernate操作重复使用CRUD映射。此解决方案的一个缺点是您以后必须在db端维护查询代码(列和映射更改等)。

修改了预订树遍历算法

这是我的ideal树持久性解决方案,因为它不需要任何stored-procedures,并且可以与NHibernateCriteria API完美集成(对我来说这是一个巨大的优势,因为我可以在代码中自由创建高级和可重用的过滤器)。从性能的角度来看,所有选择几乎都是免费的 - 您可以将平衡移向插入,更新和删除操作,因为您需要在每次更改时重新计算树 - 但这可以通过hql来完成所以(伪代码):

HQLNamedQuery hql = new HQLNamedQuery();
hql.Query = "UPDATE " + typeof(THierarchy).FullName +
    " SET TraversalLeft = (TraversalLeft + :traversalChange) " +
    " WHERE BaseNodeId = :baseNodeId AND TraversalLeft > :minTr AND TraversalLeft <= :maxTr";

我已经设法使用这种技术实现各种操作(如添加,添加范围,移动,重新排序,获取后代,获得祖先,计算后代等),我必须说,一旦你得到了基本原则,你可以完美地将它整合到不同的项目中。

链接到让我开始讨论此主题的文章(遗憾的是它没有使用NHibernate): http://weblogs.asp.net/aghausman/archive/2009/03/16/storing-retrieving-hierarchical-data-in-sql-server-database.aspx

在NHibernate中映射树

也许您还应该看一下这篇文章(确切的部分从An alternative approach开始): http://nhibernate.hibernatingrhinos.com/16/how-to-map-a-tree-in-nhibernate

这基本上归结为向映射添加两个额外的集合:AncestorsDescendants并执行三个查询:主查询,加载祖先和加载后代 - 这应该阻止NHibernate在执行时执行其他查询访问子节点。

我希望这会有所帮助。

答案 1 :(得分:0)

我认为根本原因是NHibernate无法知道查询返回层次结构中的所有记录。也就是说,它不知道节点是叶节点。怎么可能呢?

我的快速和肮脏的解决方案是向Proposal添加一个选择引导节点的方法,并用新的空集合替换代理集合。基本上你会通过调用这个方法通知Proposal它是完全填充的。