Typically with NHibernate, I’m always querying over one table and eager fetching it’s child collections. I’m also only performing WHERE conditions on that initial table I am querying over.
The other day though, I needed to solve a problem where I was adding WHERE conditions to the child collections. Now in SQL, this is quite easy but in NHibernate, it took me a while to figure out how to do this efficiently.
Example in SQL:
SELECT * FROM Customer c JOIN CustomerAddresses ca ON c.CustomerId = ca.CustomerId JOIN CustomerPhoneNumbers cpn ON c.CustomerId = cpn.CustomerId WHERE c.Status = 'Active' AND ca.City = 'Dallas' AND cpn.AreaCode = '972'
So let’s say that I have a Customer entity with a collection of CustomerAddress entities and a collection of CustomerPhoneNumber entities which might look something like this:
public class Customer { public virtual int CustomerId { get; set; } public virtual IList Addresses { get; set; } public virtual IList PhoneNumbers { get; set; } }
So my first thought is to use the .Any() method to access the child collections and put my where conditions into. Doing so I came up with the following code and although it works, the SQL it creates is inefficient by creating EXISTS clauses in the WHERE clause… very messy.
var customers = session.Query<Customer>() .Where( c => c.Status == "Active" && c.Addresses.Any( a => a.City == "Dallas" ) && c.PhoneNumbers.Any( pn => pn.AreaCode == "972" ) ) .ToList();
And here’s an example of the SQL it produces:
SELECT customer0_.CustomerId as Customer1_135_0_, customer0_.Status as Customer1_135_1_ FROM Customer customer0_ WHERE customer0_.Status='Active' /* @p0 */ AND ( EXISTS ( SELECT customeraddr1_.CustomerAddressId FROM CustomerAddresses customeraddr1_ WHERE customer0_.CustomerId=customeraddr1_.CustomerId AND customeraddr1_.City = 'Dallas' /* @p1 */ ) AND ( EXISTS ( SELECT customerphon2_.CustomerPhoneNumberId FROM CustomerPhoneNumbers customerphon2_ WHERE customer0_.CustomerId=customerphon2_.CustomerId AND customerphon2_.AreaCode = '972' /* @p2 */; ) );
But a much more efficient way would be to simply JOIN on the tables that represent the child collection properties instead of using EXISTS. To do so, I used the LINQ query syntax which allowed me to easily add multiple FROM clauses which NHibernate will interpret as INNER JOINs:
var customers = ( from c in session.Query<Customer>() from a in c.Addresses from pn in c.PhoneNumbers where c.Status == "Active" && a.City == "Dallas" && pn.AreaCode == "972" select c ) .ToList();
And here’s an example of the SQL it produces which looks remarkably like the SQL we hand coded in the beginning:
SELECT customer0_.CustomerId as Customer1_135_0_, customer0_.Status as Customer1_135_1_ FROM Customer customer0_ INNER JOIN CustomerAddresses customeraddr1_ ON customer0_.CustomerId=customeraddr1_.CustomerId INNER JOIN CustomerPhoneNumbers customerphon2_ ON customer0_.CustomerId=customerphon2_.CustomerId WHERE customer0_.Status='Active' /* @p0 */ AND customeraddr1_.City = 'Dallas' /* @p1 */ AND customerphon2_.AreaCode = '972' /* @p2 */;