2011-10-08 82 views
2

尋找一些專業知識,然後我宣佈NHibernate框架破碎或我瘋了!用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>(); 

這將正確返回所有記錄,但沒有正確填充葉子節點的子列表(應按定義爲空)。相反,當我得到一個代理和搜索樹時,成千上萬的毫無意義的零記錄查詢。

我已經試過: 1.使用左連接,而不是加入 2.使孩子們的名單不是懶惰和取=「加入」並移除HQL(作爲一個概念證明,業績下滑是不可接受的別處) 3.混淆了我沒有發現的屬性,我看到有人在休眠論壇中使用

所有這些都給了我相同的結果......一個包含所有數據(好)和數千個小查詢的大型查詢沒有數據返回(壞)。有任何想法嗎?

我使用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

我很早以前就對這個話題感興趣,所以請不要把我說的理所當然的東西(也許有更多的NHibernate經驗可以糾正我,以防萬一我錯了)。

我上次檢查描述的情況是一個相當大的問題,因爲Nhibernate正在爲子實體的每個level生成單獨的查詢。如果你事先知道你樹的深度不會太大,那麼你可能沒有孩子的eager loading ......但如果你的結構沒有深度限制,你應該考慮一下替代方案。

假設你正在使用的SqlServer

一個解決方案,我發現很容易實現的是使用recursive self-join/CTE。此解決方案涉及切換到stored-procedures作爲查詢的來源,並在代碼中手動重新創建層次結構。要獲取整個樹,您只需要一個數據庫查詢(其中可以包含各種過濾器和子查詢+排序),並且仍然可以針對所有CRUD操作重新使用映射。這種解決方案的一個缺點是你稍後必須維護數據庫端的查詢代碼(列和映射更改等)。

修改序樹遍歷算法

這是我的樹持久性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在訪問子節點時執行額外的查詢。

我希望這會有所幫助。

+0

嗯,在預置樹遍歷上有趣的想法,但遍歷樹不是我的問題,它的_getting_樹!我實際上已經研究過你的第一個解決方案(好吧,先前的'版本鏈接'),但它似乎並沒有解決nHibernate重新查詢所有葉節點的根本問題。我現在的代碼得到所有正確的數據,nHibernate似乎並不明白當你沒有孩子時缺少行是因爲他們'不存在',並不是我們還沒有爲他們查詢...... – JCFire

+0

我已經更新了我的答案 - 我已經添加了一篇關於在NHibernate中映射樹的好文章的鏈接,但說實話,我不認爲您可以輕鬆防止沒有「黑客/技巧」的附加查詢。另一方面,爲什麼您可以自己重新創建層次結構並更新映射,以便不通過代理查詢子實體? – MonkeyCoder

+0

我希望不必重新創建結構的基礎,因爲它是整個系統的基礎,但我越看這個問題,看起來似乎越像這個答案(至少在這裏另一個月對舊應用程序進行重新編碼......)我希望更多地瞭解Ayende在這裏談到的內容:[link](http://ayende.com/blog/4151/nhibernate-tips-tricks有效地選擇樹) – JCFire

0

我認爲根本原因是NHibernate無法知道查詢返回層次結構中的所有記錄。也就是說,它不知道某個節點是葉節點。它怎麼可能?

我的快速和骯髒的解決方案是將方法添加到選擇主節點並用新的空集合替換代理集合的建議。基本上你會通過Proposal通知調用這個方法完全填充它。

+0

通過這樣做不會混淆nHibernate認爲所有的葉節點是骯髒的,並使其發出相當數量的更新語句? – JCFire

+0

我不這麼認爲。即使他們被標記爲髒,他們也不會有任何成員,所以不會發出數據庫調用。檢查NHibernateUtil.IsInitialized以確定集合是否是代理,如果它是代理,則將其分配給一個空集合。 –