2008-12-02 113 views
19

我有一個3級的實體層次結構:Customer-Order-Line,我想使用ISession.Get(id)爲給定的客戶完整檢索。我有以下XML片段:NHibernate渴望獲取多個級別

customer.hbm.xml:

<bag name="Orders" cascade="all-delete-orphan" inverse="false" fetch="join"> 
    <key column="CustomerID" /> 
    <one-to-many class="Order" /> 
</bag> 

order.hbm.xml:

<bag name="Lines" cascade="all-delete-orphan" inverse="false" fetch="join"> 
    <key column="OrderID" /> 
    <one-to-many class="Line" /> 
</bag> 

我已經使用了取= 「加入」 屬性,並指示我想爲每個父代獲取子實體,並且這構建了正確的SQL:

SELECT 
    customer0_.ID AS ID8_2_, 
    customer0_.Name AS Name8_2_, 
    orders1_.CustomerID AS CustomerID__4_, 
    orders1_.ID AS ID4_, 
    orders1_.ID AS ID9_0_, 
    orders1_.PostalAddress AS PostalAd2_9_0_, 
    orders1_.OrderDate AS OrderDate9_0_, 
    lines2_.OrderID AS OrderID__5_, 
    lines2_.ID AS ID5_, 
    lines2_.ID AS ID10_1_, 
    lines2_.[LineNo] AS column2_10_1_, 
    lines2_.Quantity AS Quantity10_1_, 
    lines2_.ProductID AS ProductID10_1_ 

FROM Customer customer0_ 

LEFT JOIN [Order] orders1_ 
     ON customer0_.ID=orders1_.CustomerID 

LEFT JOIN Line lines2_ 
     ON orders1_.ID=lines2_.OrderID 

WHERE customer0_.ID=1 

到目前爲止,好 - SQL返回正確的一組記錄(只有一個獨特的OrderID),但是當我運行一個測試,以確認訂單和線實體(從NH)的正確數目,我得到錯誤的結果

應該(從我的測試數據),1xOrder和4xLine,但是,我得到4xOrder和4xLine。看起來NH並不認可結果集中「重複」的訂單信息組,也沒有正確地「重複使用」訂單實體。

我正在使用所有的整數ID(PK),並且我嘗試過使用這個ID實現T和T的IEquatable的IComparable,希望NH能看到這些實體的相等性。我也試過重寫Equals和GetHashCode來使用ID。這些「嘗試」都沒有成功。

NH是否支持「多級提取」操作,如果有,是否需要XML設置(或其他某種機制)來支持它?


注:我用熱風的解決方案與我自己的代碼進行一些修改,終於解決了這一個。 xml需要從bag更改爲set,對於所有集合,並且實體本身已更改爲實現IComparable <>,這是要建立唯一性集合的要求。

public class BaseEntity : IComparable<BaseEntity> 
{ 
    ... 

    private Guid _internalID { get; set; } 
    public virtual Guid ID { get; set; } 

    public BaseEntity() 
    { 
     _internalID = Guid.NewGuid(); 
    } 

    #region IComparable<BaseEntity> Members 

    public int CompareTo(BaseEntity other) 
    { 
     if (ID == Guid.Empty || other.ID == Guid.Empty) 
      return _internalID.CompareTo(other._internalID); 

     return ID.CompareTo(other.ID); 
    } 

    #endregion 

    ... 

} 

請注意使用InternalID字段。這對於新(瞬態)實體是必需的,否則最初他們不會有ID(我的模型在保存時會提供它們)。

+0

[這個答案](http://stackoverflow.com/questions/5266180/fighting-cartesian-product-x-join-when-using-nhibernate-3-0-0/5285739#5285739)幫助我看看如何使用QueryOver和Future查詢來熱切地獲取子孫,而不會返回重複項。該技術涉及將任務分解成單獨的SQL查詢,這些SQL查詢在數據庫的一個往返中執行。 – 2011-10-20 20:29:56

回答

21

您正在獲取4XOrder和4XLines,因爲使用連線加入結果會加倍。您可以在ICriteria上設置一個變壓器,例如:

.SetResultTransformer(new DistinctRootEntityResultTransformer()) 
+3

我發現這實際上解決了這個問題,*如果映射從bag變爲set,並且我在基類上實現必需的IComparable 。 – 2009-01-07 21:52:50

5

我剛纔讀Ayende's Blogpost在那裏他用下面的例子:

session.CreateCriteria(typeof(Post)) 
    .SetFetchMode("Comments", FetchMode.Eager) 
    .List(); 

在條件查詢,以避免在一個特定的查詢

延遲加載也許這可以幫助你。

+0

由於此評論,您只是爲我節省了幾個小時的迴歸測試。多謝。 – 2009-09-02 18:45:07

+1

這些問題表明,這不會「超過多個層面」。對多個級別使用FetchMode.Eager將產生笛卡爾積。生成正確的SQL,但NHibernate不會爲你排序。 – 2010-01-04 20:16:11

0

@Tigraine:您的查詢僅返回Post with Comments。這帶來所有評論(2個級別)的所有職位。 Ben要求客戶訂購LineItem(3級)。 @Ben:據我所知,nHibernate不支持急於加載3級。 Hibernate確實支持你。

+0

@Sheraz - 我希望你錯了:-)但是,如果你是對的,爲什麼它會產生正確的SQL?運氣? – 2008-12-02 23:02:57

0

我遇到了同樣的問題。請參閱thread。我沒有得到一個解決方案,但從法比奧的暗示。使用套裝而不是包包。它的工作。

所以我的建議是嘗試使用set。您不必使用Iesi填入收集使用IDictonary和NH很高興

public override IEnumerable<Baseline> GetAll() 
{ 
    var baselines = Session.CreateQuery(@" from Baseline b 
              left join fetch b.BaselineMilestones bm 
              left join fetch bm.BaselineMilestonePrevious ") 
              .SetResultTransformer(Transformers.DistinctRootEntity) 
              .List<Baseline>(); 
    return baselines; 
} 
1

如果你需要保持一到manys袋子,那麼你就可以發出2個查詢,每個只有1級層次的。例如,像這樣:

var temp = session.CreateCriteria(typeof(Order)) 
    .SetFetchMode("Lines", NHibernate.FetchMode.Eager) 
    .Add(Expression.Eq("Customer.ID", id)) 
    .List(); 

var customer = session.CreateCriteria(typeof(Customer)) 
    .SetFetchMode("Orders", NHibernate.FetchMode.Eager) 
    .Add(Expression.Eq("ID", id)) 
    .UniqueResult(); 

線被加載到第一個查詢的NH緩存,所以他們不會需要延遲加載時以後訪問如customer.Orders [0] .Lines [0]。