Querying Over Multiple Child Collections With NHibernate

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 */;