These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 
Author Message
 Post subject: HQL: using properties mapped in derived classes
PostPosted: Thu Feb 28, 2008 5:43 pm 
Newbie

Joined: Tue Mar 13, 2007 12:35 pm
Posts: 9
Hi,

I encountered a strange behavior and thought I should report it, it seems there might be a bug in there somewhere.

To make this more to the point, here's a concrete illustration: there's a Company class which has an IList property called Persons. There are also several classes derived from the Company - say, ClientCompany, ManufacturerCompany etc. - all stored in the same table with a discriminator field, nothing too fancy.

Now, the mistake I made was to map the Persons property not in the base Company class where it is located but in the derived classes (it is, of course, lazy-loaded). I was thinking that it would be interesting to try to control this property not using code but with NHibernate mapping ;).

And it did work until today. The problem manifested itself in a somewhat complicated combination of operations (which I'm not going to bore you with unless really necessary), but I believe it boils down to these two steps: when I did "session.Load(typeof(Company), 123)" and then an HQL like "from Company c left join fetch c.Persons where c.ID = 123", the Persons property on the object loaded in step 1 was not fetched.

Digging into NHibernate (1.2.1) source, I found out that NHibernate instantiated two different collections here: in the first operation (session.Load) an uninitialized persistent bag for the Persons collection was loaded and stored in the internal SessionImpl.collectionsByKey dictionary under a key with the real type of collection owner (say, ClientCompany). But, when HQL was executed, NHibernate tried to find this collection using a key with the wrong class (ManufacturerCompany).

Is this a bug? It does look like NHibernate was unable to find out which derived class would eventually be loaded so it guessed the type and missed. If so, it would have been more helpful if it had just thrown a an exception saying "naughty-naughty, don't do that":).

Theoretically speaking (and that is because I haven't got a clear idea as to what happens inside the NHibernate engine ;)), if the Company instance was loaded before Persons (as I suspect is true), there could be a way to make this work correctly because the real type of the collection owner would then be known.

What do you think? If more details are needed, let me know - I've got a complicated mapping and I'm too lazy to make a simpler test case, but I can run a few tests on my project (although I solved the problem - yes, I moved the property to the base class - I can re-create it anytime :)).

Regards!


Top
 Profile  
 
 Post subject:
PostPosted: Sat Mar 01, 2008 10:10 am 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
I am not even sure why the query "from Company c left join fetch c.Persons where c.ID = 123" would even work when the "Persons" collection is not mapped in the "Company" mapping. I would suggest moving the mapping of "Persons" to the "Company class. In any event, the bug (if we deem it as one) is not "why does it not work" but "why does it work". An test case would help if you can provide it.

_________________
Karl Chu


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 03, 2008 5:49 am 
Newbie

Joined: Tue Mar 13, 2007 12:35 pm
Posts: 9
Hi, thanks for the answer, now I have a better idea what to look for...

Ok, I'll try to post a simpler version of my mapping that exhibits the same behavior. Stay tuned...


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 03, 2008 7:09 am 
Newbie

Joined: Tue Mar 13, 2007 12:35 pm
Posts: 9
Wasn't as difficult as I thought... Here it is (1 meg ZIP file):

http://www.mediafire.com/?jrfjncujh9e

There's a Visual Studio 2005 windows application project and a detached MS SQL 2005 database inside. Attach the database on localhost, run the project, and click GO. The project has references to standard nhibernate installation assemblies in c:\program files (I've fixed them so it requires NH 1.2.1), you'll obviously have to change that for debugging purposes.

I made the test so it demonstrates both my points: about accepting properties mapped in derived classes and about not join fetching such properties from HQL... If the first one is a bug, then ignore the second :). There are two derived classes, and I made test cases for both (just in case), but one should be enough. I haven't verified that the code behaves exactly as I mentioned in previous posts, but it does produce similar results.

That's it. Let me know if I can do something more, or if there's a problem with the uploaded project.

Best regards!


Top
 Profile  
 
 Post subject:
PostPosted: Wed Mar 05, 2008 11:20 pm 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
Hi solari, you may have some misunderstandings on how ISession.CreateQuery() is used. CreateQuery() returns an IQuery and you have to do something to it. You snippet of code should look something like this:
Code:
ClientCompany clientCompany = (ClientCompany)session.Load(typeof(Company), 1);

// the Persons collection is lazy-load so it should be uninitialized
Debug.Assert(!NHibernateUtil.IsInitialized(clientCompany.Persons), "The Persons collection shouldn't have been initialized by session.Load. Something's wrong with test setup.");

// should this execute at all since Persons is mapped in the derived class?
IQuery q = session.CreateQuery("from Company c left join fetch c.Persons where ID = 1");
q.List();

// the join-fetch should have initialized the collection
Debug.Assert(NHibernateUtil.IsInitialized(clientCompany.Persons), "The Persons collection should have been initialized by session.Load: this is it!");

Merely creating an IQuery does not make a call to the database.

_________________
Karl Chu


Last edited by karlchu on Thu Mar 06, 2008 10:41 am, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 06, 2008 6:38 am 
Newbie

Joined: Tue Mar 13, 2007 12:35 pm
Posts: 9
Oops, my mistake, sorry... It did seem too easy, in that at least I was right :). It also has other bugs: ignore it, I'll re-post a complete project once I have it running the way it should.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 06, 2008 8:11 am 
Newbie

Joined: Tue Mar 13, 2007 12:35 pm
Posts: 9
Here it is:

http://www.mediafire.com/?x0u0g0imxu9

I uploaded the complete thing again although the database is identical. I changed a couple of details in the queries and the mapping, now it seems to work "correctly". At least now it seems to exhibit the mentioned behavior: it allows execution of the query utilizing the un-mapped Persons bag, and fails to join-fetch it. And the behavior disappears when the "Persons" mapping is moved from the derived classes to base.

Hope I got it right this time :)... Let me know if there's anything else I can do.

Cheers!


Top
 Profile  
 
 Post subject:
PostPosted: Sun Mar 09, 2008 11:10 pm 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
You are getting some concepts wrong here. Loading the manufacturerCompany on line 41 like this:
Code:
ManufacturerCompany manufacturerCompany = (ManufacturerCompany)session.Load(typeof(Company), 2);

has nothing to do with the query executed later (line 47):
Code:
IList result = session.CreateQuery("from Company c left join fetch c.Persons where c.ID = 2").List();

In other words, executing the query (line 47) does not intialize previously loaded ManufacturerCompany.

_________________
Karl Chu


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 11, 2008 6:27 am 
Newbie

Joined: Tue Mar 13, 2007 12:35 pm
Posts: 9
Hi Karl,

You may have misunderstood my intention: I didn't want to initialize the ManufacturerCompany but its Persons collection. It does seem somewhat strange in this specific example, I agree, but in the "real world" I would use this trick to lazy-load Persons collections of multiple companies with a single query. The thing is, with a proper mapping (i.e. Persons mapped in the base class), it works: the loader finds the uninitialized ManufacturerCompany.Persons#2 bag in the session and loads it correctly. However, with this "suspicious" mapping I have, the loader misses the cached object because for some reason it looks for "ClientCompany.Persons#2". (Note that I verified this behavior in the other (full) version of my project - this is a trimmed-down copy which I haven't had the time to test in detail but I believe it acts in a similar manner).

This lazy-loading issue is possibly of secondary importance here if the query is not even legal. If that is the case, I'm probably wasting your time with an unimportant detail. On the other hand, it may be interesting for someone with proper knowledge to investigate why the loader picks out a seemingly random derived class (ClientCompany) because this could indicate another bug...

Thanks!


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 11, 2008 9:50 am 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
Calling IQuery.List() does not initialize any proxy, lazily loaded collection, or interact with anything else for that matter. If you want to load manufacturerCompany.Persons, just access it.

_________________
Karl Chu


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 11, 2008 12:56 pm 
Newbie

Joined: Tue Mar 13, 2007 12:35 pm
Posts: 9
I'm afraid you lost me now... :) Maybe this will clear things up - take a look at the links below, I was under the impression this approach was fairly standard (and it does seem a rather efficient solution for loading multiple bags in one go):

http://www.ayende.com/Blog/archive/2007 ... raphs.aspx

http://osdir.com/ml/java.springframewor ... 00157.html


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.