Leandro Gomide
Leandro Gomide

Reputation: 1008

Eager load child property and grandchildren with NHibernate

I have a class model graph which looks as follows

A { ISet<B> Bset; int Id;}
B { ISet<C> Cset; D Dprop; } 
D { int Id; }

All properties are configured to be lazy loaded. I'm trying to write a query to load all the graph, starting from A. Ideally, it would be something like

var result = (from conf in s.Query<A>()
    .FetchMany(x => x.Bset)
    .ThenFetch(x => x.Dprop)
    // line below doesn't work, because x 
    //is a D object here, and not a "B", as I would like
    .ThenFetchMany(x => x.Cset) 
     where conf.Id == 42
     select conf).SingleOrDefault();

So what I need to do is "go up one level" when I try to fetch the Cset association. Does anybody knows how to do that?

I'm using Nhibernate 4.1.0.

Upvotes: 0

Views: 2232

Answers (2)

Leandro Gomide
Leandro Gomide

Reputation: 1008

A Frédréric answered, one way is to repeat the FetchMany clause, starting (from the first entity in the graph) to reach Cset again:

var result = (from conf in s.Query<A>()
.FetchMany(x => x.Bset).ThenFetch(x => x.Dprop)
.FetchMany(x => x.Bset).ThenFetchMany(x => x.Cset) 
 where conf.Id == 42
 select conf).SingleOrDefault();

using sets for associations, this solution didn't cause cartesian products in the SQL generated nor in the objects returned by NHibernate.

Another solution is use a Future Query , which would be executed when the actual query is fired (just before it):

var queryAux =  (from conf in s.Query<A>()
    .FetchMany(x => x.Bset)
    .ThenFetch(x => x.Dprop) 
    where conf.Id == 42 select conf).ToFuture();

var result = (from conf in s.Query<A>()
    .FetchMany(x => x.Bset)
    .ThenFetch(x => x.Dprop)    
     where conf.Id == 42
     select conf).SingleOrDefault();

This way, 2 queries would be fired to the database, but it also works.

Upvotes: 0

Fr&#233;d&#233;ric
Fr&#233;d&#233;ric

Reputation: 9864

You have to start over with FetchMany.

var result = (from conf in s.Query<A>()
    .FetchMany(x => x.Bset)
    .ThenFetch(x => x.Dprop)
    .FetchMany(x => x.Bset)
    .ThenFetchMany(x => x.Cset) 
     where conf.Id == 42
     select conf).SingleOrDefault();

But I fear this will cause a Cartesian product, duplicating the results and/or causing bad performances.

Better switch to lazy-loading. Avoid N+1 select issues by enabling batch loading of lazy loads.

If you need to close the session then use your entities, you will have to trigger lazy loading by calling NHibernateUtil.Initialize() on their lazily loaded properties, looping on your entities. It will not do anything on those which are already loaded thanks to lazy loading batching.
An other option is to convert your entities into something like a view model before closing the session.

Upvotes: 1

Related Questions