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:
This is not actually accurate. What is closer to the way NHibernate works is this model:
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:
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:
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
I had no idea this was possible, keep 'em coming!
@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
I imagine using this this will be tricky to implement with LINQ (or strongly named criteria) which assume that Entity Model == POCO model
The world would benefit greatly from a series titled..
"Things you didn't know about NHibernate"
This is awesome :D
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.
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?
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
is this working?
select b.Id,
b.Name,
size(b.RecentPosts)
from Blog b
where size(b.RecentPosts) > 5
Yes, it should
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.
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
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.
Hmm check that. Noop'd version is not working either.
@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.
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?
Hmm, I didn't know you could do that. Not sure how we'll support this one in Fluent NHibernate...
@Krzysztof,
It "could" work in strong type criteria, eg NHQG. Doubt it's there though atm.
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 ?
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
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.
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
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://
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.
Wayne,
Don't look at Blog.Posts, try to think about Blog.RecentPosts, or Blog.PostsByCurrentAuthor, or stuff like that.
Does noop access strategy work with simple properties (not collections)? I can't get it working...
Yes, it does.
Comment preview