Querying With NHibernate
Posted by Davy Brion on October 5th, 2008
A lot of people are rather skeptical when it comes to executing non-trivial queries with NHibernate. In this post, i want to explore some of the features that NHibernate offers to execute those kind of queries in an easy manner.
Now, the difference between easy, non-trivial and complex queries is different for everyone. So in the following example, the query that needs to be executed is not at all complex, but it isn’t your typically way too simplistic example either. It does show some often occurring requirements for queries, but at the same time it’s still small enough to grasp easily.
Suppose we have the following 4 tables:
Now, suppose we have the following business requirement: if we discontinue a product, we want to inform all of the customers who’ve ever bought that product.
NHibernate offers a few options of retrieving the customers that once bought a given product. You could use the lazy loading capabilities to walk the object graph and keep the customers you need. This approach would justify a punch in the face though. That’s just abusing lazy loading to achieve lazy coding, which is just wrong. The correct way to fetch the data is to query for it in an efficient manner.
Suppose that we would typically write the following SQL query to fetch the required data:
select customer.CustomerId, customer.CompanyName, customer.ContactName, customer.ContactTitle, customer.Address, customer.City, customer.Region, customer.PostalCode, customer.Country, customer.Phone, customer.Fax from dbo.Customers customer where customer.CustomerId in (select distinct CustomerId from Orders where OrderId in (select OrderId from [Order Details] where ProductId = 24))
For the purpose of this example, let’s just assume that the ProductId that we need is 24. Now, i’m far from a SQL guru so i don’t know if this approach (using subqueries) is the best way to fetch this data. We’ll explore another possibility later on. But for now, let’s try to get NHibernate to generate a query like the one i just showed you.
First of all, let’s focus on the following subquery:
select OrderId from [Order Details] where ProductId = 24
With NHibernate, we’d get the same thing like this:
var orderIdsCriteria = DetachedCriteria.For<OrderLine>()
.SetProjection(Projections.Distinct(Projections.Property("Order.Id")))
.Add(Restrictions.Eq("Product.Id", productId));
This basically tells NHibernate to build a query which fetches each Orders’ Id property for every Order that has an OrderLine which contains the given Product’s Id. Keep in mind that this doesn’t actually fetch the Order Id’s yet.
Now that we already have that part, let’s focus on the next subquery:
select distinct CustomerId from Orders
where OrderId in (select OrderId from [Order Details] where ProductId = 24)
With NHibernate, we’d get the same thing like this:
var customerIdsFromOrdersForProductCriteria = DetachedCriteria.For<Order>()
.SetProjection(Projections.Distinct(Projections.Property("Customer.Id")))
.Add(Subqueries.PropertyIn("Id", orderIdsCriteria));
This builds a query which returns the Customer Id for each Customer that ever ordered the given product. Notice how we reuse the previous subquery in this Criteria.
Now we need to build a query that fetches the full Customer entities, but only for the Customers whose Id is in the resultset of the previous query:
var customersThatBoughtProductCriteria = DetachedCriteria.For<Customer>()
.Add(Subqueries.PropertyIn("Id", customerIdsFromOrdersForProductCriteria));
That’s pretty easy, right? This is the query that NHibernate sends to the database to fetch the data:
SELECT
this_.CustomerId as CustomerId0_0_,
this_.CompanyName as CompanyN2_0_0_,
this_.ContactName as ContactN3_0_0_,
this_.ContactTitle as ContactT4_0_0_,
this_.Address as Address0_0_,
this_.City as City0_0_,
this_.Region as Region0_0_,
this_.PostalCode as PostalCode0_0_,
this_.Country as Country0_0_,
this_.Phone as Phone0_0_,
this_.Fax as Fax0_0_
FROM dbo.Customers this_
WHERE
this_.CustomerId in
(SELECT distinct this_0_.CustomerId as y0_ FROM dbo.Orders this_0_
WHERE this_0_.OrderId in
(SELECT distinct this_0_0_.OrderId as y0_ FROM dbo.[Order Details] this_0_0_ WHERE
this_0_0_.ProductId = @p0));
Apart from the aliases that were added, this looks exactly the same as the query i wrote manually.
An extra benefit that i think is pretty important is that each part of the query is actually reusable. If you built an API that could give you each part of the entire query that you needed, then you could easily reuse each part whenever you needed it. Duplication in queries is just as bad as duplication in code IMHO.
Suppose you’d want to limit the amount of subqueries and use a join instead of the lowest level subquery. If we’d write the query ourselves, it would look something like this:
select customer.CustomerId, customer.CompanyName, customer.ContactName, customer.ContactTitle, customer.Address, customer.City, customer.Region, customer.PostalCode, customer.Country, customer.Phone, customer.Fax from dbo.Customers customer where customer.CustomerId in (select o.customerId from Orders o inner join [Order Details] line on line.OrderId = o.OrderId where line.ProductId = 24)
First, let’s try to write the following query with NHibernate’s Criteria API:
select o.customerId
from Orders o inner join [Order Details] line on line.OrderId = o.OrderId
where line.ProductId = 24
Since our Order class has an OrderLines collection that is mapped to the [Order Details] table, we can generate that part of the query like this:
var customerIdsFromOrdersForProductCriteria = DetachedCriteria.For<Order>()
.SetProjection(Projections.Distinct(Projections.Property("Customer.Id")))
.CreateCriteria("OrderLines", JoinType.InnerJoin)
.Add(Restrictions.Eq("Product.Id", productId));
The final part remains the same:
var customersThatBoughtProductCriteria = DetachedCriteria.For<Customer>()
.Add(Subqueries.PropertyIn("Id", customerIdsFromOrdersForProductCriteria));
And the query that NHibernate generates looks like this:
SELECT
this_.CustomerId as CustomerId0_0_,
this_.CompanyName as CompanyN2_0_0_,
this_.ContactName as ContactN3_0_0_,
this_.ContactTitle as ContactT4_0_0_,
this_.Address as Address0_0_,
this_.City as City0_0_,
this_.Region as Region0_0_,
this_.PostalCode as PostalCode0_0_,
this_.Country as Country0_0_,
this_.Phone as Phone0_0_,
this_.Fax as Fax0_0_
FROM
dbo.Customers this_
WHERE
this_.CustomerId in
(SELECT distinct this_0_.CustomerId as y0_
FROM dbo.Orders this_0_ inner join dbo.[Order Details] orderline1_ on this_0_.OrderId =
orderline1_.OrderId WHERE orderline1_.ProductId = @p0);
Again, pretty easy right?
The Criteria API’s Projection features, combined with Subqueries and combining Criteria into larger Criteria offers you a lot of possibilities when it comes to querying. This post only showed a very small part of what’s available, but hopefully it’s enough to point some people in the right direction. Now, NHibernate’s criteria API is pretty powerful, but the learning curve is indeed somewhat steep. It does take a while to get used to it, and i certainly don’t know everything there is to know about it either. But it’s definitely worth investing some time into learning how to use it well.

October 5th, 2008 at 1:06 pm
[...] NOTE: this was originally posted on my own blog [...]
October 5th, 2008 at 1:25 pm
Disco Stu would do it like this:
selectcustomer.CustomerId,
customer.CompanyName,
customer.ContactName,
customer.ContactTitle,
customer.Address,
customer.City,
customer.Region,
customer.PostalCode,
customer.Country,
customer.Phone,
customer.Fax
from
Customers Customer
join (
select
Orders.CustomerID
from
Orders
join [Order Details] on [Order Details].OrderID = Orders.OrderID
where
[Order Details].ProductID = 24
group by
Orders.CustomerID) ProductCustomer on ProductCustomer.CustomerID = Customer.ID
October 5th, 2008 at 1:27 pm
scratch that! => like this
selectcustomer.CustomerId,
customer.CompanyName,
customer.ContactName,
customer.ContactTitle,
customer.Address,
customer.City,
customer.Region,
customer.PostalCode,
customer.Country,
customer.Phone,
customer.Fax
from
Customers Customer
join (
select distinct
Orders.CustomerID
from
Orders
join [Order Details] on [Order Details].OrderID = Orders.OrderID
where
[Order Details].ProductID = 24) ProductCustomer on ProductCustomer.CustomerID = Customer.ID
October 5th, 2008 at 1:37 pm
yeah, i’d usually try to do it with joins instead of any subqueries as well. But as far as i know (and if someone can correct this, please do so) you can only join on associations that have been mapped with NHibernate. So joining on ‘dynamically’ composed resultsets is something that is not possible, unless i’m mistaken. Maybe someone else can shed more light on this
Having said that, i’d like to know which aproach is better: the joins or the subqueries. Theoritically speaking, i’d think the join approach is better. But these days, you’re never really sure how the DB is going to optimize the query when it’s actually being executed so i wouldn’t be surprised if the subquery approach turns out to be just as fast as the joined approach.
October 6th, 2008 at 9:05 am
[...] Querying With NHibernate – Davy Brion explores the construction of complex queries in NHibernate using the criteria API, including looking at creating sub queries, and compares the underlying SQL to a hand crafted original. [...]
October 6th, 2008 at 9:08 am
heh, now this is interesting… one of our database specialists at work told me that subqueries were indeed not the best way to fetch the data
so we compared the performance of the query with the subqueries, compared to one that uses only joins (and some variations on that)
in each case, the one with subqueries performed better
i think it’s because the resultset of the subqueries is in this case the same for each row in the main query. if the subqueries where using values of the rows in the main query, the join approach would probably be a lot faster i think