Lazy Loading issues
I’m pretty new to NHibernate and Fluent NHibernate, so please bear with me. :-)
I have a class "Group" which holds a list of "Item" class objects. (Obviously I've changed a few names to make the question more generic.) The "Item" class has a reference to an "Address" class. The "Address" class has two subclasses, "DlsAddress" and "NtsAddress". I never instantiate "Address" directly; all addresses are either of type "DlsAddress" or "NtsAddress". These two address subtypes both have their ToString() methods overridden. I also have a "SpecificTypeOfItem" which inherits from "Item".
In some test code, I create some new objects of the types listed above and save them to the database within a transaction (using the Session object). The database already contains other objects from previous runs of this test. I then start a new transaction and retrieve some objects into a collection using:
IList specificItemList = session.CreateCriteria(typeof(SpecificTypeOfItem)).SetMaxResults(10).List< SpecificTypeOfItem >();
Then I iterate through this list calling ToString() on the retrieved items' addresses.
What happens is that I get the proper result from the items I just created previously in this run (that is, I get a properly formatted address that uses my ToString() override on the Address subtypes), but for the items that are in the database from previous runs, I get "AddressProxy(long string of letters an numbers)". If I try casting the Address as the appropriate subclass (using "as") it comes back as null (meaning the cast failed).
I thought that lazy loading was supposed to retrieve the "real" object when it was referenced.
At this point, I figured that even though I'm not sure why this isn't working, I really didn't need Addresses to be loaded lazily so I modified my automapping Fluent Nhibernate call and added overrides so that Address and its subclasses wouldn't be loaded lazily:
... .Override
(map => { LazyLoad.Never(); }) .Override(map => { LazyLoad.Never(); }) .Override(map => { LazyLoad.Never(); }) ...This did not appear to have changed anything as I'm still getting Address proxies.
Any ideas? Am I missing something obvious?
Myles
Support Staff 2 Posted by Hudson Akridge on 02 Mar, 2010 05:33 PM
You'll get proxies regardless of lazy loading, it's to let NH know whether or not objects are dirty.
It would help us out to get a list of your XML mappings since you appear to be doing automapping.
Please call .ExportTo(directory) and attach them here. Thanks!
3 Posted by MylesRip on 02 Mar, 2010 06:49 PM
I have attached the relevant hbm files. Note that "Address" has been renamed to "Location", "Item" is "AssessedItem", and "Group" is "Property". (Sorry for the confusion! My attempt to make things more generic backfired.)
I noticed that, even though I overrode "Location" and its subclasses setting lazy loading to false, the hbm file still has: default-lazy="true".
4 Posted by MylesRip on 03 Mar, 2010 09:56 PM
I did some more experimenting in which I compared the SQL that is generated and the hbm files that are generated. What I found is that the following overrides had no impact at all on either the generated hbm files or the generated SQL.
.Override(map => { LazyLoad.Never(); }) .Override(map => { LazyLoad.Never(); }) .Override(map => { LazyLoad.Never(); }) .Override(map => { LazyLoad.Never(); })
If I use Conventions.Add(DefaultLazy.Never()), then I no longer have problems with the proxy because the entire object graph gets pulled in, but that defeats the whole purpose of lazy loading.
Am I correct in assuming that I should not be having this problem at all? Shouldn't additional queries be issued automatically when the proxied items are referenced such that when I work with the objects, I should never even be aware that the whole object graph wasn't returned initially?
Something is wrong here.
Support Staff 5 Posted by James Gregory on 03 Mar, 2010 10:00 PM
Ah, you've got a bit of a mixup going on with your overrides.
The
LazyLoad.Never()
method you're calling is actually a static helper for creating conventions, it's not modifying the mapping as you'd expect. You need to callmap.LazyLoad.Never()
instead. It's an easy mistake to make.6 Posted by MylesRip on 03 Mar, 2010 10:05 PM
Looking at what I just posted, I see that for some reason the angle brackets and their contents disappear. The four override statements above set LazyLoad.Never() for the following classes:
7 Posted by MylesRip on 03 Mar, 2010 10:22 PM
Hmm. The syntax has me stymied. I tried replacing the LazyLoad.Never() with map.LazyLoad.Never(), but the compiler doesn't like it. Where do I place the statements? Currently my configuration statement looks like this:
(I used the preview feature to make sure the angle brackets above are working. Hopefully it will look right after I post it. ;-) )
Support Staff 8 Posted by James Gregory on 03 Mar, 2010 10:35 PM
You just need to change your
Override
calls to use the signature I gave.For example, this:
Becomes this:
Note: The curly brackets { and } are optional when you're using a single line call, so the above could also be rewritten as:
9 Posted by MylesRip on 03 Mar, 2010 11:07 PM
The syntax isn't working for me. When I try:
.Override<Location>(map => { map.LazyLoad.Never(); })
or
.Override<Location>(map => map.LazyLoad.Never())
The compiler says:
"void ClassMap<Location>.LazyLoad()
Error: FluentNHibernate.Mapping.ClassMap<Location>.LazyLoad() is a 'method', which is not valid in the given context."
When I type "map." then Intellisense kicks in and shows me the LazyLoad method, but after I type "map.LazyLoad." Intellisense does not show any options.
Support Staff 10 Posted by James Gregory on 04 Mar, 2010 10:43 AM
Ah, damn. That's my mistake, sorry. I was doing it from memory, and obviously my memory isn't that great.
LazyLoad
is a method when you're inside aClassMap
(or override), and it's toggled using theNot
property. So to lazy-load an entity, you'd saymap.LazyLoad()
, to not lazy-load an entity you'd saymap.Not.LazyLoad()
.So the correct syntax is:
Sorry about that.
11 Posted by MylesRip on 04 Mar, 2010 05:56 PM
Oh that's much better! Thanks for your help, James (and patience!). It fixed my immediate problem where I decided not to lazy load the locations, but I'm still confused as to why the proxy didn't automatically resolve the reference when it was used. Here is a simple, crude test.
If the Location is not loaded lazily, the above code shows the name of the proxy for the Location.ToString() method call instead of the override version defined on DlsLocation. Also, the cast to DlsLocation fails. (For this test, only DlsLocations exist in the data.)
Back in the days of doing lazy loading with a hand-built DAL, the getter for well.LegalSurveyLocation would have set off an additional query and instantiated the Location object so that the calling code would be ignorant of the fact that it didn't exist previously.
Perhaps as I learn more about NHibernate, the reason this isn't working the way I expected will become clearer to me... (I hope!)
By the way, don't you ever sleep? ;-)
Support Staff 12 Posted by James Gregory on 09 Mar, 2010 02:27 PM
That's how NHibernate works too. It could be the way you've got things configured. If you're still having this issue, feel free to post your mappings and I can take a look.
Sorry for the slow reply, I've been sleeping ;)
13 Posted by MylesRip on 09 Mar, 2010 04:08 PM
Good morning! :-)
I still have the problem if I comment out the override:
//.Override<Location>(map => { map.Not.LazyLoad(); })
The problem only happens for records that were added to the database in a previous run of the test. The records that I add immediately before retrieving the data work correctly. The output for the code in my previous post looks like this:
well = 505
location = LocationProxy3c0ba952a1f84ac5b40794747717f0df
well = 1010
location = LocationProxy3c0ba952a1f84ac5b40794747717f0df
well = 1515
location = LocationProxy3c0ba952a1f84ac5b40794747717f0df
well = 1516
location = LocationProxy3c0ba952a1f84ac5b40794747717f0df
well = 2020
location = LocationProxy3c0ba952a1f84ac5b40794747717f0df
well = 2021
location = LocationProxy3c0ba952a1f84ac5b40794747717f0df
well = 2525
township = 126
location = 16-36-126-30W3
well = 2526
township = 072
location = 12-14-072-22W4
If I comment out the line
if (loc1 != null)
then I get a NullReferenceException on the following line.(By the way, I just noticed that in my previous post, it should have read, "If the Location is loaded lazily, the above code...." )
I have attached the relevant mapping files that I get when the override is commented out as above.
Thanks in advance for your help!
14 Posted by MylesRip on 15 Mar, 2010 02:48 PM
I know y'all are busy. I was just wondering if any progress has been made on this. That is, any ideas as to why the proxies fail to resolve?
15 Posted by ramin on 28 Apr, 2010 09:59 AM
I had the same problem. You cannot have lazy="true" on a superclass with joined-subclasses. If superclass "person" has a joined-subclass "legalperson" and "naturalperson", and lazy="true" on "person", then the generated SQL for loading "person" does not include the left outer joins for "legalpeson" and "naturalperson", and the "person" proxy is loaded as a "person", and not as one of the subclasses it is supposed to be loaded as.
In other words, NHibernate loads the proxy as a "person", and casting it to either natural- or legalperson will result in null. Only with lazy="false" on "person" will a left outer join be produced, and then the proxy can be cast.
I think that is a bug, because NHibernate should not apply lazy="true" to the joined-subclasses. But then again, once you know about this, you can work around it.
enjoy
16 Posted by Dmitry on 18 Jun, 2010 04:53 AM
Hello!
I have the same problem with lazy loading of subclasses as described above. Could someone tell how to solve it?
17 Posted by Dmitry on 18 Jun, 2010 07:25 AM
And one interesting thing else: having done several tests I find out what LazyLoad() method has no effect in SubclassMap. Only LazyLoad() or Not.LazyLoad() in superclass mapping (ClassMap) is important.
Please, write here the current state of this problem.
18 Posted by Dmitry on 21 Jun, 2010 08:35 AM
Hello, sample of code demonstrating this problem is below.
Objects:
{mkd-extraction-f81462625391ea945eee04fb1ae7279a}Mapping:
{mkd-extraction-5e5da02d9cda1775e5e4baf0c4dace9b}Test:
{mkd-extraction-6ce16038a9b0548afb79db9200bbf983}Result:
flag {false}
order.GetType() {Name = "OrderProxy7f1017cca0254b28b3029b63014f6b27" FullName = "OrderProxy7f1017cca0254b28b3029b63014f6b27"} System.Type {System.RuntimeType}
I exported mappings to hbm.xml - everything is ok. I tried to Get() this object:
Order __order = session.Get((Int64)500);
it is ok too.
19 Posted by Dmitry on 21 Jun, 2010 08:40 AM
Hello, sample of code demonstrating this problem is below.
Objects:
Mapping:
Test:
Result:
flag {false}
order.GetType() {Name = "OrderProxy7f1017cca0254b28b3029b63014f6b27" FullName = "OrderProxy7f1017cca0254b28b3029b63014f6b27"} System.Type {System.RuntimeType}
I exported mappings to hbm.xml - everything is ok. I tried to Get() this object:
Order __order = session.Get((Int64)500);
it is ok too.