1

My entity "TimeRecord" has a collection of "WayPoints" and two one-to-one properties "Location" and "WayData".

Each property can be null.

I need to export all Time Records with initialized properties for a specific User.

I actually had a working solution but then I started to use NHibernateProiler and first I noticed that this code results in a ridiculous number of query’s against db.

var query = (from timeRecord in Session.Query<TimeRecord>()
                .Where(tr => tr.User.Id == userid)
            select timeRecord);

Then I changed my code to:

var query = (from post in Session.Query<TimeRecord>()
                .Fetch(x => x.Location)
                .Fetch(x => x.WayData)
                .FetchMany(x => x.WayPoints)
                .Where(tr => tr.User.Id == userid)
            select post);

Which lead me to the Cartesian product problem.

Right now I’m experimenting with this piece of code:

 var sql1 = "from TimeRecord b left outer join fetch b.Location where b.User.Id=:User_id";
 var sql2 = "from TimeRecord b left outer join fetch b.WayData where b.User.Id=:User_id";
 var sql3 = "from TimeRecord b left inner join fetch b.WayPoints where b.User.Id=:User_id";


var result = Session.CreateMultiQuery()
             .Add(Session.CreateQuery(sql1))
             .Add(Session.CreateQuery(sql2))
             .Add(Session.CreateQuery(sql3))
             .SetParameter("User_id", userid)
             .List();

But I can’t say if this is the correct approach or if this is even possible with nHibernate. Can someone help me with that?

Moshe Katz
  • 15,992
  • 7
  • 69
  • 116
stefan
  • 1,336
  • 3
  • 21
  • 46
  • Cartesian product problem? Does the query produce duplicate posts, or do you mean that it produces SQL that has #TimeRecords x #WayPoints in its result set? The latter would be normal. I though NHibernate would return distinct results despite of `FetchMany`. – Gert Arnold Jan 15 '14 at 21:01
  • It works as described here: http://nhforge.org/blogs/nhibernate/archive/2008/09/06/eager-loading-aggregate-with-many-child-collections.aspx -> Please notice that you have three joins, an inner and two outer joins. NHibernate returns a single set of records containing the cross product of all three joins. That is: if a blog has one author, 10 posts and 100 readers the result set will have 1*10*100 = 1000 records. If you have 100 posts and 1000 readers you will get a set with 100'000 records! – stefan Jan 15 '14 at 21:37
  • What matters is: how many records will be returned. I agree that `Fetch(Many)` will blow up the SQL result set but AFAIK (I'm more in Entity Framework) only unique posts will be returned with loaded references and collection. Of course you can look for ways to make it more efficient under the hood, e.g. with futures: http://stackoverflow.com/a/5225939/861716. – Gert Arnold Jan 15 '14 at 21:47
  • I agree that EF would be better for that. I always pick the wrong one :( Last time I used it I had a lot of N:N relations which wasn't really supported by EF at that time so a felt the decision to use nHibernate the next time for as persistence layer. – stefan Jan 16 '14 at 09:08
  • No, EF is not better. NHibernate's ability to batch queries is quite unique. EF's `Include` statement will blow up the SQL result set just as badly. – Gert Arnold Jan 16 '14 at 09:12
  • Ok, I just can remember that in EF I always had to use this Include all the time and in the article I've read (posted as response to your comment) they took EF's Include as reference and tried to implement the same behavior in NH... – stefan Jan 16 '14 at 09:34
  • Oops! Thats the link where the compare it with EF: http://ayende.com/blog/4367/eagerly-loading-entity-associations-efficiently-with-nhibernate – stefan Jan 16 '14 at 09:38

1 Answers1

1

The 1+N issue is a usual with the entity/collection mapping and ORM tools. But NHibernate has a very nice solution how to manage it properly. It is called:

This setting will allow:

  • To continue querying the root entity (TimeRecord in our case)
  • No fetching inside of the query (Session.Query<TimeRecord>()). That means we do have support for correct paging. (Take(), Skip() will be executed over the flat root table)
  • All the collections will be loaded with their own SELECT statements (could seem as disadvantage but se below)
  • There will be much more less SELECTs then 1+N. All of them will be batched. E.g. by 25 records
  • all the native mapping (lazy loading of collections) will still be in place...

The xml mapping example:

-- class level
<class name="Location" batch-size="25 ...

-- collection level
<batch name="Locations" batch-size="25" ...

I would suggest to apply that over all your collections/classes. With Fluent mapping it could be done also with conventions

The fluent mapping:

// class
public LocationMap()
{
    Id(x => x....
    ...
    BatchSize(25);

// collection
HasMany(x => x.Locations)
  ...
  .BatchSize(25);
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • I'm still investigating (learning) ... But wouldn't the right query be better than changing the mapping layer? As far as I investigated the problem a subselect should do the job for me… – stefan Jan 16 '14 at 10:02
  • This approach is, let's say here for us, exactly for these scenarios. It does subselect in fact. Exactly does. But out of the box... just with a setting `BatchSize`. My experience is: have it everywhere... do not care about lazy loading... it will be efficient ;) Do it once during the mapping, get it all the time during the querying ;) – Radim Köhler Jan 16 '14 at 10:24
  • So it does not hurt to have it :P Many thanks for that but I want to try it with the subselects and DetachedCriteria because I want to learn how to do such complex loading... – stefan Jan 16 '14 at 10:50
  • I am not sure if I did express all that correctly. Anyhow: Here you can get some inspiration about how to do complex subqueries: http://stackoverflow.com/a/20537260/1679310. But for loading the root entity, and its collections... I would always use the BatchSize(). Good luck with NHibernate anyway ;) – Radim Köhler Jan 16 '14 at 11:05
  • I have an android application which allows the user to track his way and attach it to a timerecord. There can be a lot of waypoints 50km way is ~ 1000waypoints. If a users wants to have his data on another device the android app connects to a WCF service and needs all the data for the specific user. So my requirement is quite easy: I just want that my object graph TimeRecord -> WayPoints are loaded and send to the device. Here starts my problem with loading strategies. When I do nothing, just the default implementaion, NHibernate performs a lazy load for every WayPoint collection... – stefan Jan 16 '14 at 11:25
  • The way I handle *(but it is my personal peference only)* these scenarios, is DTO and some custom `CopyInto()`. So, I get from NHibernate the flat root object. Then I do iterate that ... and fill per partes the DTO, representing what should be sent. During these *copy into* steps, NHibernate uses batch-size loading... so it is efficient. At the end, DTO contains only what I want to send, and using laziness.. NHibernate loaded only the needed stuff, but per partes.. in batches. Just my way. another view ;) – Radim Köhler Jan 16 '14 at 11:28
  • I use DTO too but my problem is how to get the data most efficient. If I wouldn't use NHibernate I would just load each Table manually, create some kind of index (Dictionary, Hashtable...) and sort my data manually into the correct entities... I may also don't know enough about batch-size but this seems quite static to me and my collection size (WayPoints) have never the same lenght. – stefan Jan 16 '14 at 11:37
  • Stefan, I do respect your case! What I am trying to say: I do my best to profit from NHibernate features and leave the easy part for me ;) It is easier to create in C# `CoppyInto()` DTO object, while iterating the simple result loaded by NHibernate. And it does populate on the ran as needed all related collections. In comparison with doing the oposite: manually select and subselect all the data and then also create the DTO. While subqueries are great... it will require more "not needed effort". Stefan, I really do understand that you need your way. Just wanted to give another view ;) – Radim Köhler Jan 16 '14 at 11:40
  • I spent the last two days just with investigating that and it must have something to do with Futures, Multiqueries and DetachedCriteria... – stefan Jan 16 '14 at 11:41
  • Many thanks for your input!! But I want to make it with the built in features of NHibernate so that at the end of the day I've learned something new :) – stefan Jan 16 '14 at 12:13
  • Good luck with NHibernate. It is amazing tool sir ;) – Radim Köhler Jan 16 '14 at 12:18