2008-12-02 115 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]。