使用Fluent NHibernate/Nhibernate和Automapping进行Eager Loading

25

我有一个需要加载名为Node的复杂对象的要求……其实它并不是那么复杂……看起来像这样:

Node引用了EntityTypeEntityTypeProperty具有一对多关系,而Property又与PorpertyListValue具有一对多关系。

public class Node
{
    public virtual int Id
    {
        get;
        set;
    }

    public virtual string Name
    {
        get;
        set;
    }

    public virtual EntityType Etype
    {
        get;
        set;
    }

}


public class EntityType
{
    public virtual int Id
    {
        get;
        set;
    }

    public virtual string Name
    {
        get;
        set;
    }

    public virtual IList<Property> Properties
    {
        get;
        protected set;
    }

    public EntityType()
    {
        Properties = new List<Property>();
    }
}

public class Property
{
    public virtual int Id
    {
        get;
        set;
    }

    public virtual string Name
    {
        get;
        set;
    }        

    public virtual EntityType EntityType
    {
        get;
        set;
    }

    public virtual IList<PropertyListValue> ListValues
    {
        get;
        protected set;
    }

    public virtual string DefaultValue
    {
        get;
        set;
    }

    public Property()
    {
        ListValues = new List<PropertyListValue>();
    }
}


public class PropertyListValue
{
    public virtual int Id
    {
        get;
        set;
    }

    public virtual Property Property
    {
        get;
        set;
    }

    public virtual string Value
    {
        get;
        set;
    }

    protected PropertyListValue()
    {
    }
}

我想做的是一次性加载所有子对象到Node对象中,不使用Lazy load。原因是我的数据库中有成千上万个Node对象,我必须使用WCF服务将它们发送到网络。我遇到了SQL N+1问题。我正在使用Fluent Nhibernate和Automapping,并且NHibernate Profiler建议我使用FetchMode.Eager一次性加载整个对象。我正在使用以下查询:

     Session.CreateCriteria(typeof (Node))
            .SetFetchMode( "Etype", FetchMode.Join )
            .SetFetchMode( "Etype.Properties", FetchMode.Join )
            .SetFetchMode( "Etype.Properties.ListValues", FetchMode.Join )

或者使用 NHibernate LINQ

        Session.Linq<NodeType>()
         .Expand( "Etype")
         .Expand( "Etype.Properties" )
         .Expand( "Etype.Properties.ListValues" )
无论我运行上述查询中的任何一个,它们都会生成一个具有所有左外连接的单个查询,这正是我所需要的。但出于某些原因,从查询返回的 IList 没有被正确地加载到对象中。实际上,返回的 Nodes 数量等于查询结果的行数,因此 Nodes 对象是重复的。此外,每个 Node 中的属性也是重复的,Listvalues 也是如此。
因此,我想知道如何修改上述查询以返回所有唯一的 Nodes 及其中的属性和 Listvalues。

在谷歌上,我了解到DistinctRootEntityResultTransformer的存在,但它仅解决根对象的问题。我的子集合仍然有重复。返回列表中的每个根对象都在子集合中具有一些奇怪的笛卡尔积混乱,其中包含多个相同实体的实例。你有什么想法吗?等待中 Nabeel - nabeelfarid
1
我想我已经找到了解决方法,但我想知道它是否正确。在根对象(Node)中的子集合(EType.Properties,Etype.Properties.ListValues) 是IList。 我在文档中读到,IList可以包含重复项,因此,如果我将IList更改为 ISet / ICollection,那么查询就不会在子集合中加载重复的实例。但这个解决方案需要大量的重构。 我想知道是否有一种方法可以使用IList来实现相同的结果?等待中, Nabeel - nabeelfarid
1
我也遇到了同样的问题(使用Fetchmode.Eager)。对于NHibernate的这个问题,我感到非常失望。与其得到不正确的数据,我宁愿得到一个错误提示。 - UpTheCreek
4个回答

23

2
谢谢您的回复,Tim。我不想在映射中设置.Not.LazyLoad(),因为这将成为默认行为,在我的应用程序中,我有一个WCF服务需要将数据传递给客户端,并且我希望在单个查询中一次性加载所有数据,以避免SQL N+1场景(http://nhprof.com/Learn/Alerts/SelectNPlusOne)。应用程序的其余部分不需要急切加载。那么,您有什么想法可以解决这种情况吗? - nabeelfarid
1
我的理解是.Not.LazyLoad不能解决SQL N+1问题。从Nhibernate分析器中我注意到,虽然它一次性加载了整个对象图,但仍会生成多个查询,每个引用/hasmany对象一个查询,而我不想要这样的查询,因为我有成千上万个节点,拥有数百个实体类型和属性,我不希望生成成千上万个唯一的查询。 - nabeelfarid
1
嗨Tim,谢谢你提供的信息。实际上,我并没有生成查询的问题。根据nHibernate分析器显示,生成的查询是正确的。奇怪的是,我的对象返回列表中有重复项,并且返回列表中每个根对象的子集合中都有一些奇怪的笛卡尔积混乱,其中包含多个相同实体的实例。但是,使用DistinctRootEntityResultTransformer并将子集合替换为ICollection而不是IList后,它按照我预期的方式工作。但我想知道这是否是正确的解决方案? - nabeelfarid
如果你得到的是ICollection而不是IList,那很可能是你映射成了set而不是bag。如果你想要set,那么ICollection就是正确的选择。 - Tim Hoolihan
我和Tim nabeeelfarid遇到了完全相同的问题,但是我正在使用Fetchmode.Eager。 - UpTheCreek
显示剩余3条评论

14

我自己找到了答案。关键在于使用SetResultTransformer(),并将DistinctRootEntityResultTransformer对象作为参数传递。因此查询现在看起来像下面这样:

Session.CreateCriteria(typeof (Node))
   .SetFetchMode( "Etype", FetchMode.Join )
   .SetFetchMode( "Etype.Properties", FetchMode.Join )
   .SetFetchMode( "Etype.Properties.ListValues", FetchMode.Join )
   .SetResultTransformer(new DistinctRootEntityResultTransformer());

我通过以下链接找到了答案:

http://www.mailinglistarchive.com/html/nhusers@googlegroups.com/2010-05/msg00512.html

http://ayende.com/Blog/archive/2010/01/16/eagerly-loading-entity-associations-efficiently-with-nhibernate.aspx


4
+1,但哇,这太丑了。这似乎是一团糟 - 我对NHibernate并不印象深刻。为什么我们需要转换结果?看起来NH没有很好地完成它的工作。 - UpTheCreek

9
我最终得到了以下内容:
HasMany(x => x.YourList).KeyColumn("ColumnName").Inverse().Not.LazyLoad().Fetch.Join()

请确保像这样选择您的实体,以避免因连接而导致重复:

session.CreateCriteria(typeof(T)).SetResultTransformer(Transformers.DistinctRootEntity).List<T>();

1
你也可以用引用来做到这个吗? - aggietech

4
使用DistinctRootEntityResultTransformer的SetResultTransformer只适用于主对象,但IList集合会被乘以倍数。

你会如何使用 ISet 或 ICollection? - Guillermo Gutiérrez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接