Joins

Join syntax of SQL allows you to connect two database tables which share a column value. On most occasions, the shared columns constitute foreign key relation between two tables but having such relationship is not mandatory. As long as the columns hold the value referring to the same record, you can run a meaningful join query. For instance, in our domain, the Employee table's Id column and the Benefit table's Employee_Id column hold the Id of particular employee record. We can join the two tables using value in those two columns.

Different types of joins

There are mainly two types of joins. An inner join and an outer join. The best way to understand the difference between the two is to use Venn diagram from set theory. If you think of employee as one set and benefit as another set, then intersection between the two can be represented by a Venn diagram as follows:

Different types of joins

The overlapping area or intersection of the Employee and Benefit sets represents the records that are present in both sets. These are employee records having associated benefit records or vice versa. The non-overlapping area represents the records that are either present only in the Employee set or the Benefit set.

A SQL join lets you connect two tables and return records that are either present in both tables or present in one of the tables. If you want the records that are present in both the tables, then you should use inner join. If you want records that are present in either of the tables then you use an outer join. Following modified Venn diagram depicts which is the inner join and which is outer:

Different types of joins

Outer joins have three subtypes – a left outer join, a right outer join, and a full outer join. A left outer join would return records that are present in the overlapping part of the preceding Venn diagram plus the records that are present in the left non-overlapping part. A right outer join returns records that are present in the right non-overlapping part and the overlapping part of the preceding Venn diagram. A full outer join is combination of right and left outer join. Syntactically, following is how you write these different joins:

SELECT *
FROM Employee E
INNER JOIN Address A ON E.Id = A.Employee_Id

SELECT *
FROM Employee E
LEFT OUTER JOIN Address A ON E.Id = A.Employee_Id

SELECT *
FROM Employee E
RIGHT OUTER JOIN Address A ON E.Id = A.Employee_Id

SELECT *
FROM Employee E
FULL OUTER JOIN Address A ON E.Id = A.Employee_Id

The equality construct after the preceding ON is how your associate records from two sets. This is mostly a foreign key relationship between the two tables. Most relational database technologies support both inner and outer joins but there may be a few which do not. If you are using such database, then NHibernate would throw an exception if you happen to use outer join while it is not supported.

Since NHibernate knows from the mapping metadata how two entities are related to each other, it takes away the complexity and makes joining two entities simple. This is also called implicit joins.

Implicit joins

NHibernate supports notion of implicit join. It means that if you have mapped two entities as having association between them, then NHibernate would implicitly join the mapped tables when one entity is navigated from the other in the query. This could be difficult to understand, so let's take an example. Suppose you want to load all employees who live in London. You would need an inner join between the Employee and Address tables to load those employees. Since we have mapped the association between the Employee and Address class, NHibernate knows to use inner join when the ResidentialAddress property on the Employee class is navigated in LINQ query, as shown next:

[Test]
public void QueryEmployeesWhoLiveInLondon()
{
  IList<Employee> employees = null;
  using (var transaction = Database.Session.BeginTransaction())
  {
    employees =
    Database.Session.Query<Employee>()
    .Where(e => e.ResidentialAddress.City == "London").ToList();
    transaction.Commit();
  }

  Assert.That(employees.Count, Is.EqualTo(3));
}

Take a look at the part in bold where we specify restriction on the City property just by navigating the ResidentialAddress property of Employee. Because of the mapped association between the two classes, NHibernate knows that a foreign key relationship exists between the tables that these entities are mapped to. NHibernate will use that knowledge to generate the following SQL involving an inner join between the Employee and Address tables:

SELECT employee0_.id             AS Id0_, 
       //…other columns…// 
FROM   employee employee0_ 
       inner join address address1_ 
               ON employee0_.id = address1_.employee_id 
WHERE  address1_.city =@ p0; 
@p0 = 'London' [TYPE: String (0)] 

Criteria and QueryOver support implicit joins but the syntax is slightly verbose. Following is how the preceding query can be written using QueryOver:

[Test]
public void QueryEmployeesWhoLiveInLondon()
{
  IList<Employee> employees;
  using (var transaction = Database.Session.BeginTransaction())
  {
    employees = Database.Session.QueryOver<Employee>()
    .JoinQueryOver(x => x.ResidentialAddress)
    .Where(r => r.City == "London").List<Employee>();
    transaction.Commit();
  }

Assert.That(employees.Count, Is.EqualTo(3));
}

For LINQ version, the navigation of the ResidentialAddress property inside call to the Where method gave us the join we needed. But that does not work with QueryOver. In QueryOver, you need to explicitly tell NHibernate about your intention to perform a join by calling method named JoinQueryOver. It is in this method that you navigate to the ResidentialAddress property. Call to JoinQueryOver returns a new instance of IQueryOver<Address>, so any subsequent restrictions are applied on properties of the Address entity. Internally, there is a lot going on here. Let's take a look at this in detail to understand how exactly joins work for QueryOver.

QueryOver joins explained

The QueryOver examples we have seen so far have used the ISession.QueryOver<T> method. This method returns an instance of IQueryOver<T1, T2> where both T1 and T2 are of same type T. For instance, when we called ISession.QueryOver<Employee>, we got IQueryOver<Employee, Employee>.

T1 is the root entity which we are trying to retrieve from database. It is obvious that terminal methods such as List and Select can only use T1. T2 is used in the Where method to declare filters or restrictions on the entities being retrieved.

T1 cannot change after an IQueryOver<T1, T2> is instantiated. T2 changes every time you introduce a join clause by calling JoinQueryOver. The lambda expression passed into JoinQueryOver determines the new type of T2. In our case, we passed x => x.ResidentialAddress into JoinQueryOver which changed T2 from Employee to Address. An immediate effect of this is that you can now apply restrictions only on the properties of the Address entity (remember restrictions can only be applied on T2?).

Another point to note is that you can only make further joins on associations that are present on T2. Again, in our previous example, T2 was Employee to being with, so we could join on ResidentialAddress which is present on T2 (or Employee). After this join, T2 changes and hence further joins can only be applied on associations that are present on the new T2, Address. This is a problem. What if you want to run another join on the Benefits association present on root entity Employee? Well, there is a solution to that. But before I get into the solution, let me take a moment to talk about another problem you may hit with this.

The restrictions that you can apply in the Where method can only be applied on the properties of T2. So in our example, you can only apply restriction on properties of the Address entity. What if we want to retrieve employees having first name John and who live in London? Solution to both this and the previous problem is to use JoinAlias which is what we are going to look at next.

JoinAlias

Similar to JoinQueryOver, JoinAlias lets you join on an association present on T1 but it does not change T2. Rather it gives you a new type that you can use wherever you want in your query. Following query retrieves employees having first name John and who live in London:

Address addressAlias = null;
var employees = Database.Session.QueryOver<Employee>()
.JoinAlias(x => x.ResidentialAddress,() => addressAlias)
.Where(e => e.Firstname == "John" && addressAlias.City =="London")
.List<Employee>();

We are using JoinAlias here. JoinAlias takes in two parameters. First one is a lambda to the association to be joined. So far this is same as JoinQueryOver. Second one is Expression<Func<T>> type of parameter which returns a local variable of type T. In the preceding example, we chose T to be of type Address. If I had to explain in plain English what this code is doing, then it is like telling the following to QueryOver:

"Hey. QueryOver, I want you to join on the ResidentialAddress property which is present on your root type Employee. I also want you to use my local variable addressAlias to represent this join so that I can use my local variable further down the line in this query if I have to."

Simple isn't it? And then as you can see, we have two restrictions applied in the Where method. One is on FirstName which is present on Employee and another on City for which we have used our local variable addressAlias.

You can call JoinAlias as many times as you want. The only downside of that is, your queries would start looking a bit ugly with too many local alias variables. This is where I prefer the power of LINQ which interprets an implicit join as you navigate the association inside the Where method. Following is how the same query written in LINQ would look:

var employees = Database.Session.Query<Employee>()
.Where(e => e.Firstname == "John" &&
e.ResidentialAddress.City == "London").ToList();

Note

I have chosen to skip explaining criteria query for an implicit join as I do not want to encourage you to use criteria queries if you can use LINQ or QueryOver. But if for any reason you must use criteria, feel free to explore it on your own.

Outer joins

In QueryOver, specifying left or right outer joins is not very different from specifying inner joins. Rather, the implicit join syntax can be slightly modified to tell NHibernate that it should consider an outer join in place of inner join. The QueryOver example we just saw can be changed as follows to result in a left outer join instead of inner join:

using (var transaction = Database.Session.BeginTransaction())
{
  employees = Database.Session.QueryOver<Employee>()
  .Left.JoinQueryOver(x => x.ResidentialAddress)
  .Where(r => r.City == "London").List<Employee>();
  transaction.Commit();
}

The preceding code is exact reproduction of the earlier code except for one minor modification in bold. Call to chained property Left before JoinQueryOver tells NHibernate that a left outer join should be considered here. Similarly, chained properties Right and Full are available to declare our intent to consider right outer join and full outer join respectively.

Support for outer joins in LINQ is limited and syntax is not intuitive. For starters, you cannot use lambda expressions for outer joins. Following is an example of left outer join between Employee and Benefit using a LINQ query:

                 from b in e.Benefits.DefaultIfEmpty()
                 select e).ToList();

Note the call to DefaultIfEmpty() in bold after join with e.Benefits. This is LINQ's implementation of left join. What this is telling NHibernate is that if benefit side is empty then consider a default record – which is what a left join does. A caveat to remember with this syntax is that it only works for the joins with the properties that can be navigated from the root entity. In the preceding query, root entity is Employee and the joined entity is Benefit which can be navigated from Employee via the Benefits collection on it.

Theta style joins

The joins we have seen so far are called ANSI joins. There is another class of joins called theta style joins. Theta style joins are similar to Cartesian product of two tables with a where clause limiting the records that end up in the result. The previous query to return employees living in London can be rewritten using theta style join as follows:

SELECT employee0_.id             AS Id0_, 
       //… other columns …// 
FROM   employee employee0_, 
       address address1_ 
WHERE  address1_.employee_id = employee0_.id 
       AND address1_.city =@ p0; 
@p0 = 'London' [TYPE: String (0)]  

Theta joins are useful when you need to join two entities without having any mapped association between them (or two database tables not related through a foreign key). It is strange to have two entities which are related but do not have that relation reflected in database or in mappings. But you may encounter such situation while working with legacy databases, so it is useful to know how to do theta joins. Lack of association between the entities means that we cannot use implicit joins. Unfortunately, not all querying methods of NHibernate support theta joins. Only HQL and LINQ queries support theta joins. Following is an example of theta join using LINQ that would result in the previous SQL being sent to database:

var employees = from e in Database.Session.Query<Employee>()
                join a in Database.Session.Query<Address>() 
on e equals a.Employee
                where a.City == "London"
                select e;

Here, instead of navigating the ResidentialAddress property on the Employee class, we are getting a new independent IQueryable<Address> instance in order to perform a join. This results in a theta style join being issued.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.224.226