Ayende @ Rahien

Refunds available at head office

NHibernate – query only properties

With most applications, there is some difference between the domain model and the data model. Probably the most common scenario that I run into can be expressed in the usually Blog & Posts example. In our domain model, we don’t want to have a Blog.Posts collection, we want to only have a Post.Blog.

However, in the data model, all associations are bidirectional, but that doesn’t mean that we want to have the same in the domain model. The problem is that we still want to query on that. That is a bummer, right? Because now we have to change our domain model to fit the query model that we want.

Not really. It is not well known, because we don’t generally think about this as an important concept that should be externalized to the users, but while we usually portray NHibernate working on the following model:

image

This is not actually accurate. What is closer to the way NHibernate works is this model:

image

And the reason that this is important is that the entity model doesn’t have to match the POCO model.

Let us see how this works:

<set name="Posts"
     access="noop">
    <key column ="BlogId"/>
    <one-to-many class="Post"/>
</set>

As you can see, we specify a no op access strategy (with NH 2.1 you can also use access=”none”, which sounds nicer), to do so. Now, we can execute a query:

var blog = 
    s.CreateQuery("from Blog b where size(b.Posts) > 5")
    .List<Blog>();

Remember, Blog doesn’t have a Posts property, it is a query only property. This code results in:

image

This is actually most useful when you want to have different projections on the same thing. Let us take a look:

<set name="RecentPosts"
     where="(PostedAt >= (getdate() - 30) )"
     access="noop">
    <key column ="BlogId"/>
    <one-to-many class="Post"/>
</set>

And using this just as before:

var blog =
       s.CreateQuery("from Blog b where size(b.RecentPosts) > 5")
       .List<Blog>();

And that will result in this:

image

I think that this is the key usage scenario for this feature, being able to create query only associations that I’ll traverse only for querying.

Comments

Benny Michielsen
06/10/2009 06:36 PM by
Benny Michielsen

I had no idea this was possible, keep 'em coming!

Dave the Ninja
06/10/2009 07:17 PM by
Dave the Ninja

@Benny - neither did I!!!

@Ayende - Time to refactor some code written last night to do this very thing - time to remove the noisey POCO collection reference!

CHEERS!!!

Ninja

Krzysztof Kozmic
06/10/2009 08:09 PM by
Krzysztof Kozmic

I imagine using this this will be tricky to implement with LINQ (or strongly named criteria) which assume that Entity Model == POCO model

Shane Courtrille
06/10/2009 09:30 PM by
Shane Courtrille

The world would benefit greatly from a series titled..

"Things you didn't know about NHibernate"

This is awesome :D

Gunnar Liljas
06/10/2009 09:34 PM by
Gunnar Liljas

It can be implemented with LINQ if you're not afraid of exposing primary and foreign keys as properties. Not beautiful, but it's actually just a bit of noise.

Stephen
06/10/2009 09:49 PM by
Stephen

I guess technically you could have entity types and linq against them.. very hacky though..

Ayende you say: "In our domain model, we don’t want to have a Blog.Posts collection, we want to only have a Blog.Post."

Did you mean Post.Blog? otherwise I don't follow..

And can you explain why you wouldn't want Blog.Posts on the poco? just the example scenario, or good practice? is this related to aggregate roots?

Ayende Rahien
06/10/2009 10:54 PM by
Ayende Rahien

Yes, that was a typo.

And there are a few reasons, most of them relating to the fact that you don't want to have highly connect object models, or that if you would try to create those associations, you would have association explosion

Jose
06/10/2009 11:25 PM by
Jose

is this working?

select b.Id,

b.Name,

size(b.RecentPosts)

from Blog b

where size(b.RecentPosts) > 5

Min Han
06/10/2009 11:38 PM by
Min Han

These recent posts on NHibernate are really interesting. I would have no idea how I would have come upon this particular gem without this post.

Richard Dingwall
06/11/2009 03:14 AM by
Richard Dingwall

I didn't know this was possible either, very cool. It looks like it works for version columns too (who cares abouts optimistic locking implementation in the domain model).

I even tried it on an ID column (value type in domain, but has an ID in the legacy relational schema), but that failed with this exception:

NHibernate.HibernateException : identifier of an instance of SMS.Domain.Osm.CoreCompetencyRequirements was altered from 12189 to

:O might log a bug report

Michael Teper
06/11/2009 03:17 AM by
Michael Teper

Hmm, that's really interesting, but only as a curiosity. It actually looks like an antipattern to me because the mapping, which is supposed to be an implementation detail is now required to understand query syntax, in addition to the POCO model.

Richard Dingwall
06/11/2009 03:23 AM by
Richard Dingwall

Hmm check that. Noop'd version is not working either.

Gerke Geurts
06/11/2009 08:23 AM by
Gerke Geurts

@Michael Teper: If query implementation is the responsibility of a repository/query service, just like the mapping definition, then I don't see any design conflicts.

Frans Bouma
06/11/2009 08:27 AM by
Frans Bouma

I really don't understand why anyone would want to add query predicates to a mapping file in XML and use that in a string based query in C#. If something will become a maintenance nightmare this is probably it.

Not having a field mapped onto a relationship is not that uncommon, but mainly in o/r mapper frameworks which do reverse relationship maintenance (i.e. myOrder.Customer = myCustomer; also adds myOrder to myCustomer.Orders), as you sometimes don't want the PK side to know about all the FK sides it has, for example because it's kept in memory and could lead to problems.

As the other side of the relationship does have a field mapped (Post.Blog), isn't that enough to query?

Paul Batum
06/11/2009 11:59 AM by
Paul Batum

Hmm, I didn't know you could do that. Not sure how we'll support this one in Fluent NHibernate...

Hendry Luk
06/12/2009 05:07 AM by
Hendry Luk

@Krzysztof,

It "could" work in strong type criteria, eg NHQG. Doubt it's there though atm.

Denis
06/15/2009 01:17 PM by
Denis

You say that "there are a few reasons" for not exposing Blog.Posts.

I'm wondering whether one of this reasons is that such one-to-manymany relationships are "dangerous" with regard to performance. Hiding them can reduce the risk of developpers using them and forgetting what happens under the hood. Is it the case ?

Ayende Rahien
06/15/2009 03:37 PM by
Ayende Rahien

That is one of them, for sure. There are also implications from the point of view of the domain model, but mostly it is because a lot of those associations just doesn't really make sense as an association on the objects

Chris Nicola
06/17/2009 06:57 AM by
Chris Nicola

Interesting, though I may be a bit confused as to the difference between the entity model and the POCO. The POCO is the domain model I take it, but then what is the nature of the mapping from entities to POCOs?

Also, if I were creating a UI using MVVM does that mean I would have yet another mapping of the viewmodel to the POCOs. Seems overkill perhaps, but it is more than likely I am just misunderstanding. I just don't want to get into the mistake of creating redundant code.

Ayende Rahien
06/17/2009 10:20 AM by
Ayende Rahien

Chris,

That is why this is hidden very deeply inside NHibernate, where you don't usually see it.

From your perspective, you can treat POCO as the final model

Wayne Douglas
06/17/2009 01:10 PM by
Wayne Douglas

I think of this as saying this:

in the EM that we want to have a virtual property mapped one-to-many to another entity. The POCO has no concept of the relationship - only the entity model expressed through config?

I think I get all the concepts here and it's definitely very cool - what I'm struggling with - as were a couple others is why Blog.Posts is a bad idea - seems like the most natural thing in the world to me :s

Is this because, semantically, you think it's wrong to have this relationship?

Cheers

w://

Chris Nicola
06/17/2009 03:10 PM by
Chris Nicola

Ah I understand what you're saying now. This is definitely a useful feature I can imagine a number of situations where I could use it.

I feel like I may have made a mistake with my bi-directional associations though. I have a sort of three level tree of one-two many associations where I query the root entities and then load the collections.

Ayende Rahien
06/18/2009 04:20 AM by
Ayende Rahien

Wayne,

Don't look at Blog.Posts, try to think about Blog.RecentPosts, or Blog.PostsByCurrentAuthor, or stuff like that.

Alexander Kovtik
07/20/2009 11:24 AM by
Alexander Kovtik

Does noop access strategy work with simple properties (not collections)? I can't get it working...

Comments have been closed on this topic.