Ayende @ Rahien

Refunds available at head office

NHibernate new feature: No proxy associations

About three weeks ago I introduced the problem of ghost objects in NHibernate. In short, given the following model:

image

This code will not produce the expected result:

var comment = s.Get<Comment>(8454);
if(comment.Post is Article)
{
   //
}

You can check the actual post for the details, it related to proxying and when NHibernate decides to load a lazy loaded instance. In short, however, comment.Post is a lazy loaded object, and NHibernate, at this point in time, has no idea what it is. But since it must return something, it returns a proxy of Post, which will load the actual instance when needed. That leads to some problems when you want to down cast the value.

Well, I got fed up with explaining about this and set about to fix the issue. NHibernate now contains the following option:

<many-to-one name="Post" lazy="no-proxy"/>

When lazy is set to no-proxy, the following things happen:

  • The association is still lazy loaded (note that in older versions of NHibernate, setting it to no-proxy would trigger eager loading, this is no longer the case).
  • The first time that you access the property the value will be loaded from the database, and the actual type will be returned.

In short, this should completely resolve the issue.

However, not the key phrase here, like lazy properties, this work by intercepting the property load, so if you want to take advantage of this feature you should use the property to access the value.

Comments

Tyler Burd
01/28/2010 02:12 PM by
Tyler Burd

This is great! I know that it violates Liskov and such, but so many new/junior programmers get bitten by this when they first introduce inheritance in their model.

Cassio Tavares
01/28/2010 02:38 PM by
Cassio Tavares

Really Great, but do you recommend use it in all many-to-one associations? Is there a rule?

Steve Degosserie
01/28/2010 02:56 PM by
Steve Degosserie

Why not load the type of the associated object during the initial query (might need to add some joins depending on the inheritance storage strategy chosen) so that a proxy of the correct type can be generated ?

Ayende Rahien
01/28/2010 03:07 PM by
Ayende Rahien

No, I don't.

If you can, keep the current behavior. This requires NH to do more work and it encourage bad design practices

Ayende Rahien
01/28/2010 03:07 PM by
Ayende Rahien

Steve,

You can do that if you want to eager load.

But in some cases, you want to do things lazily.

Steve Degosserie
01/28/2010 03:17 PM by
Steve Degosserie

I mean, NH could automatically pre-load to the entity type of lazy many-to-one association.

If I do a comparison 'comment.Post.Id == 123', you know it better than me, Post is not going to be lazy-loaded because the proxy holds the Id. Similarly, I would expect lazy-loading not to be triggered if I do 'comment.Post is Article'.

Ayende Rahien
01/28/2010 03:26 PM by
Ayende Rahien

Steve,

That implies eager loading, the same cost is associated with this or with eager loading.

Gamlor
01/28/2010 04:11 PM by
Gamlor

Just out of curiosity (I'm not a NHibernate-user). The Comment.Post-property has to be virtual in order to allow this behaviour, right?

Ayende Rahien
01/28/2010 04:36 PM by
Ayende Rahien

Gamlor,

In general, all properties/methods must be virtual.

Martin Aatmaa
01/29/2010 06:42 AM by
Martin Aatmaa

To rephrase Cassio Tavares' question:

Do you recommend to use it in all many-to-one polymorphic associations?

Ayende Rahien
01/29/2010 08:13 AM by
Ayende Rahien

Martin,

Again, no. Use it if you need it, but I don't like needing it

Ayende Rahien
01/29/2010 08:14 AM by
Ayende Rahien

Alex,

I still don't agree with your reasoning.

And it takes a single query.

Alex Yakunin
01/30/2010 11:55 AM by
Alex Yakunin

Ah, yes... Surely there is a single query when yo materialize a proxy. But what happens if you access a property of inherited type later?

Concerning the reasoning - well, deeds say more than words.

Alex Yakunin
01/30/2010 11:56 AM by
Alex Yakunin

when yo materialize a proxy

Sorry, wrong description, I mean "when NH resolves the reference".

Ayende Rahien
01/30/2010 12:00 PM by
Ayende Rahien

Alex,

If you access the property, there is no query.

re: deeds, there are things that I do because people want it enough, not because I think it is good

Alex Yakunin
01/30/2010 07:26 PM by
Alex Yakunin

there are things that I do because people want it enough, not because I think it is good

Good answer ;)

If you access the property, there is no query.

So may be I'm missing something. While doing the first query, you actually don't know the exact type of referenced entity (of course, if there is inheritance hierarchy with type discriminator), and thus you don't know how to completely fetch the instance.

Querying all the tables from the hierarchy is, likely, a bad idea as well, because there can be lots of them.

So how such case is handled?

Ayende Rahien
01/30/2010 07:41 PM by
Ayende Rahien

When you access the property on the root entity, we load the value (one query).

Alex Yakunin
01/31/2010 09:05 AM by
Alex Yakunin

Let's imagine we have Person.Pet reference of Animal type, and the instance it refers can actually be of type Cat, Dog, ... any other of its 20 inheritors (so the hierarchy is mapped to 20 different tables). And we use class per table inheritance mapping with type discriminator.

Let's think I run the following code:

var person = ...; // Person instance is loaded

var pet = person.Pet; // Animal instance is loaded, but in which part?

var dog = pet as Dog;

if (dog!=null) {

double barkVolume = dog.BarkVolume; // Any DB interaction?

}

Ayende Rahien
01/31/2010 12:19 PM by
Ayende Rahien

Alex,

var person - db query

person.Pet - db query.

That is all

ivos
02/01/2010 05:13 AM by
ivos

Nice! just a question: all these new proxy/lazy loafing new features are here because hibernate has them or just because they are really good? The proper question should be: is nhibernate still following hibernate's steps?

Ayende Rahien
02/01/2010 08:16 AM by
Ayende Rahien

Ivos,

NHibernate still follows in Hibernate footsteps, but is not limited to them

Alex Yakunin
02/01/2010 04:46 PM by
Alex Yakunin

person.Pet - db query

Which tables will be used by this query in described case?

Ayende Rahien
02/01/2010 04:50 PM by
Ayende Rahien

Alex,

Similar to what would happen if I did:

session.GetPet

Alex Yakunin
02/01/2010 05:01 PM by
Alex Yakunin

If I understand everything correctly, earlier session.GetPet would return a proxy, which type isn't dependent on actual Pet type there (i.e. won't be able to successfully cast it to e.g. Dog).

The query that would run in this case fetches just Dog's fields, so this line must lead either to DB interaction, or to unexpected result:

double barkVolume = dog.BarkVolume; // Any DB interaction?

But since you describe new functionality, it's desirable to describe what's changed in this case.

Alex Yakunin
02/01/2010 05:18 PM by
Alex Yakunin

(i.e. won't be able to successfully cast it to e.g. Dog).

I mean "I won't be able to ..."

The query that would run in this case fetches just Dog's fields

Sorry, "Pet's fields."

Ayende Rahien
02/01/2010 05:41 PM by
Ayende Rahien

Alex,

Get does NOT return a proxy.

Alex Yakunin
02/01/2010 06:23 PM by
Alex Yakunin

Now it's clear that your statement about session.Get was correct. I tried to find out what actually NHibernate does to load an instance mapped with "joined-subclass" strategy, and actually failed. I see two basic options here:

a) Do this using a single query involving all the tables in the hierarchy ( or b) a part of them, if entity type is partially known - e.g. as T generic argument of session.Get method)

c) Query for hierarchy root type, detect actual entity type and run a precise query involving just necessary tables.

So what algorithm is used by NHibernate in this case? A, b, c, a combination of some of them, etc.?

Alex Yakunin
02/01/2010 06:34 PM by
Alex Yakunin

Thanks for a link - there is really a perfect example.

So just to confirm: does NHibernate always use way a) ?

Ayende Rahien
02/01/2010 06:44 PM by
Ayende Rahien

Alex,

To perform polymorphic query, yes

Alex Yakunin
02/01/2010 06:45 PM by
Alex Yakunin

Ok, thanks. Thinking on consequences...

Comments have been closed on this topic.