Friday, September 21, 2012

C#: Left outer joins with LINQ


Original link: http://smehrozalam.wordpress.com/2009/06/10/c-left-outer-joins-with-linq/

C#: Left outer joins with LINQ

I always considered Left Outer Join in LINQ to be complex until today when I had to use it in my application. I googled and the first result gave a very nice explanation. The only difference between ordinary joins (inner joins) and left joins in LINQ is the use of “join into” and “DefaultIfEmpty()” expressions.
Consider this very simple query (Assuming a scenario that not all the TimesheetLines are associated with a Job)
1Select TL.EntryDate, TL.Hours, J.JobName
2From TimeSheetLines TL
3Left Join Jobs J on TL.JobNo=J.JobNo
A LINQ query using inner join is
01var lines =
02    from tl in db.TimeSheetLines
03    join j  in db.Jobs on tl.JobNo equals j.JobNo
04    where tl.ResourceNo == resourceNo
05 
06    select new
07    {
08        EntryDate = tl.EntryDate,
09        Hours = tl.Hours,
10        Job = j.JobName
11    };
And a LINQ query performing left join is
01var lines =
02    from tl in db.TimeSheetLines
03    join j  in db.Jobs on tl.JobNo equals j.JobNo into tl_j
04    where tl.ResourceNo == resourceNo
05 
06    from j in tl_j.DefaultIfEmpty()
07    select new
08    {
09        EntryDate = tl.EntryDate,
10        Hours = tl.Hours,
11        Job = j.JobName
12    };
Notice that the only difference is the use of “into” with the join statement followed by reselecting the result using “DefaultIfEmpty()” expression. And here’s the generated SQL for the above LINQ expression.
1SELECT [t0].[EntryDate] as [EntryDate], [t0].[Hours] as [Hours], [t1].[JobName] AS [Job]
2FROM [dbo].[TimeSheetLine] AS [t0]
3LEFT OUTER JOIN [dbo].[Jobs] AS [t1] ON [t0].[JobNo] = [t1].[JobNo]
4WHERE [t0].[ResourceNo] = @p0
Another LINQ version which is more compact is:
1var lines =
2    from tl in db.TimeSheetLines
3    from j in db.Jobs.Where(j=>j.JobNo == tl.JobNo).DefaultIfEmpty()
4    select new
5    {
6        EntryDate = tl.EntryDate,
7        Hours = tl.Hours,
8        Job = j.JobName
9    };
Similarly, this concept can be expanded for multiple left joins. Assuming that a TimeSheetLine will either have a JobNo or an IndirectCode, consider this SQL query:
1Select TL.EntryDate, TL.Hours, J.JobName, I.IndirectName
2From TimeSheetLines TL
3Left Join Jobs J on TL.JobNo=J.JobNo
4Left Join Indirects I on TL.IndirectCode=I.IndirectCode
The equivalent LINQ query is:
01var lines =
02    from tl in db.TimeSheetLines
03    join j in db.Jobs      on tl.JobNo        equals j.JobNo         into tl_j
04    join i in db.Indirects on tl.IndirectCode equals i.IndirectCode  into tl_i
05    where tl.ResourceNo == resourceNo
06 
07    from j in tl_j.DefaultIfEmpty()
08    from i in tl_i.DefaultIfEmpty()
09    select new
10    {
11        EntryDate = tl.EntryDate,
12        Hours = tl.Hours,
13        Job = j.JobName,
14        Indirect = i.IndirectName,
15    };
And the generated SQL is:
1SELECT [t0].[EntryDate] as [EntryDate], [t0].[Hours] as [Hours], [t1].[JobName] AS [Job], [t2].[IndirectName] As [Indirect]
2LEFT OUTER JOIN [dbo].[Jobs] AS [t1] ON [t0].[JobNo] = [t1].[JobNo]
3LEFT OUTER JOIN [dbo].[Indirects] AS [t2] ON [t0].[IndirectCode] = [t2].[IndirectCode]
4WHERE [t0].[ResourceNo] = @p0

That’s all, left outer joins in LINQ are as easy as in T-SQL. Happy joining.
Update:
Notice that this post describes the approach to perform a Left Outer Join in LINQ To SQL as well as Entity Framework (version 4). The same is not true for Entity Framework version 3.5 since it does not support the DefaultIfEmpty keyword. To perform Left Outer Joins with Entity Framework 3.5, we need to create appropriate relationships (e.g 0..1 to 0..Many) in our Entity Model and they will be automatically translated into TSQL’s Left Join clause.