views:

449

answers:

2

Hi guys,

I have a requirement to load a complex object called Node...well its not that complex...it looks like follows:-

A Node has a reference to EntityType which has a one to many with Property which in turn has a one to many with 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()
    {
    }
}

What I a trying to do is load the Node object with all the child objects all at once. No Lazy load. The reason is I have thousands of Node objects in the database and I have to send them over the wire using WCF Service.I ran into the classes SQL N+ 1 problem. I am using Fluent Nhibernate with Automapping and NHibernate Profiler suggested me to use FetchMode.Eager to load the whole objects at once. I am using the following qyuery

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

OR using NHibernate LINQ

        Session.Linq<NodeType>()
         .Expand( "Etype")
         .Expand( "Etype.Properties" )
         .Expand( "Etype.Properties.ListValues" )

When I run any of the above query, they both generate one same single query with all the left outer joins, which is what I need. However, for some reason the return IList from the query is not being loaded property into the objects. Infact the returned Nodes count is equal to the number of rows of the query, so the Nodes objects are repeated.Moreover, the properties within each Node are repeated, and so do the Listvalues.

So I would like to know how to modify the above query to return all unique Nodes with the properties and list values within them.

Thanks Nabeel

+2  A: 

each mapping has to have lazy loading off

in Node Map:

Map(x => x.EntityType).Not.LazyLoad();

in EnityType Map:

Map(x => x.Properties).Not.LazyLoad();

and so on...

Also, see http://stackoverflow.com/questions/563250/nhibernate-eager-loading-multi-level-child-objects for one time eager loading

Added:

Additional info on Sql N+1:

http://nhprof.com/Learn/Alerts/SelectNPlusOne

Tim Hoolihan
Thanks for the response Tim,Well I do not want to set the .Not.LazyLoad() in the mapping because it will then become default behaviour, and in my application I have a wcf service that that needs to pass data to the client and I want to load all the data at once in a single query to avoid SQL N+1 scenario (http://nhprof.com/Learn/Alerts/SelectNPlusOne). The rest of the application does not require eagerloading. So any idea how can I tackle this scenario?
nabeelfarid
Also my understanding is that .Not.LazyLoad does not solve the SQL N+1 problem. Fro mNhibernate profiler I have noticed that although it loads all the whole object graph in one go, it still generates more than one query, a query for each reference/hasmany object, that I do not want because I have thosands of nodes with hundered of entitytypes and properties and I do not want thosands unique queries to be generated.Nabeel
nabeelfarid
I thought you wanted it mapped that way. I added a link to another question that shows eager loading in a particular instance. I think this will help you produce a join. If not, you could look at righting a stored procedure and mapping it as a named query. See the original posters code in http://stackoverflow.com/questions/1637862/fluent-nhibernate-and-stored-procedures for an example of that
Tim Hoolihan
also, check out http://nhforge.org/blogs/nhibernate/archive/2008/09/06/eager-loading-aggregate-with-many-child-collections.aspx
Tim Hoolihan
Hi Tim, thanks for the info. Actually I am not having problem with generating the query. The query that gets generated is correct as exposed by nHibernate profiler. What strange is that the returned list of my objects has duplicats and every root object in the returned list has some weird Cartesian product mess in the childcollections with multiple instances of the same entity but after using DistinctRootEntityResultTransformer and replacing child collections with ICollection instead of IList, it is working as I expected it to. But I would like to know if its the right solution or not ?
nabeelfarid
If you are getting ICollection instead of IList, it probably means you have mapped as a set instead of bag. If set is what you want, then I collection is right.
Tim Hoolihan
also check out FetchMode.Eager to try to load the objects
Tim Hoolihan
+1  A: 

I figure it out myself. The key is to use SetResultTransformer() passing an object of DistinctRootEntityResultTransformer as a parameter. So the query now looks like as follows

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

I found the answer to my questions through these links:

http://www.mailinglistarchive.com/html/[email protected]/2010-05/msg00512.html

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

nabeelfarid