148

Is this query equivalent to a LEFT OUTER join?

//assuming that I have a parameter named 'invoiceId' of type int
from c in SupportCases
let invoice = c.Invoices.FirstOrDefault(i=> i.Id == invoiceId)
where (invoiceId == 0 || invoice != null)    
select new 
{
      Id = c.Id
      , InvoiceId = invoice == null ? 0 : invoice.Id
}
Svetlozar Angelov
  • 21,214
  • 6
  • 62
  • 67
Ali Kazmi
  • 3,610
  • 6
  • 35
  • 51

6 Answers6

238

You don't need the into statements:

var query = 
    from customer in dc.Customers
    from order in dc.Orders
         .Where(o => customer.CustomerId == o.CustomerId)
         .DefaultIfEmpty()
    select new { Customer = customer, Order = order } 
    //Order will be null if the left join is null

And yes, the query above does indeed create a LEFT OUTER join.

Link to a similar question that handles multiple left joins: Linq to Sql: Multiple left outer joins

Community
  • 1
  • 1
Amir
  • 9,091
  • 5
  • 34
  • 46
  • 16
    While I know that @Marc Gravvel's answer does work, I really prefer this method because IMO it feels more in line with what a left join should look like. – llaughlin Aug 06 '12 at 15:30
  • 1
    Excellent answer. Looking for more than 5 hours of google search. This is the only way resulting SQL will have left join in it. – Faisal Mq Jul 13 '13 at 22:01
  • 1
    THANK YOU soooo much....I was searching for a solution for this all afternoon and your code nailed it (and feels natural to boot). Wish I could upvote this several times. – Jim Sep 12 '13 at 20:52
  • 2
    @Jim thanks :-) I'm glad devs are still getting mileage out of this answer. I completely agree that the DefaultIfEmpty() feels a lot more natural than using the into statements. – Amir Sep 13 '13 at 15:35
  • This works even better than the `into` linq query I first came up with! – jao Apr 22 '15 at 13:14
  • 1
    If there's another table you need to join to, what would be the process? I tried to add another table and I got the error "Non-static method requires a target" – Batuta May 27 '15 at 19:53
  • lovely. now i can use boolean expressions again instead of just the equals operator – symbiont Jun 08 '16 at 07:53
  • 8
    Just a note for anyone else who finds this like I just did, this results in a LEFT OUTER JOIN *inside* a CROSS APPLY, which means you will get duplicates if there are multiple matches on the right-hand side of the join. Marc Gravell's solution, while not as "pretty" gave me the proper SQL output and result set that I was looking for. – Mike U Jun 21 '16 at 14:48
  • @MikeU Nice addition. I do believe that is how a `LEFT OUTER JOIN` normally works, is it not? If there are multiple items on the right, the left item is repeated to accommodate them. – Timo Jul 13 '20 at 15:06
181

Not quite - since each "left" row in a left-outer-join will match 0-n "right" rows (in the second table), where-as yours matches only 0-1. To do a left outer join, you need SelectMany and DefaultIfEmpty, for example:

var query = from c in db.Customers
            join o in db.Orders
               on c.CustomerID equals o.CustomerID into sr
            from x in sr.DefaultIfEmpty()
            select new {
               CustomerID = c.CustomerID, ContactName = c.ContactName,
               OrderID = x == null ? -1 : x.OrderID };   

(or via the extension methods)

logical8
  • 1,092
  • 9
  • 12
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 28
    Can someone explain how this crazy syntax works? I fail to see how any of those keywords magically makes it a left join. What does the "into sr" do? Linq frustrates me sometimes :) – Joe Phillips Apr 07 '14 at 21:29
  • 4
    @JoePhillips I have plenty of SQL experience but trying to learn LINQ is like wading through mud. I agree it is absolutely crazy. – Nick.Mc Jun 12 '14 at 03:41
  • @marc-gravell:Could you help me in solving my sql query to linq conversion : http://stackoverflow.com/questions/28367941/unable-to-convert-sql-query-to-linq-query-for-left-outer-join – Vishal I P Feb 09 '15 at 07:35
  • @VishalIPatil *why* do you want to convert from SQL to LINQ? SQL works just fine and is far more predictable and efficient... – Marc Gravell Feb 09 '15 at 08:32
  • @MarcGravell:Actually we are using linq queries in our MVC RAZOR Project,and i'm more friendly with SQL compared to LINQ.I'm struggling to write LINQ query,whereas i can easily write this in SQL.Since In our project all queries are in LINQ,i'm trying to write in LINQ. – Vishal I P Feb 09 '15 at 10:22
  • 1
    @VishalIPatil so... why do that? Just about every LINQ tool includes the ability to run hand-written SQL. Why not just do that? – Marc Gravell Feb 09 '15 at 14:57
  • if `OrderID` is not a `Nullable` but just an `int`, we should do `x.OrderID > 0 ? -1 : x.OrderID`??? – Jaider Mar 02 '17 at 15:20
  • This answer worked best when joining data from tables in two different databases. You have to use ToList() after each from statement, but it worked great. – nixkuroi Jun 12 '18 at 21:23
  • 1
    @JoePhillips, I think `into sq` is called a grouping in LINQ terms. It's the virtual result of the join operation and is a collection made up of all the other joined objects, but seems to also have the properties of the right side of the join. Like `x.Customer` and `x.Order` and also x.AllPropertiesOfOrder (because they are looping through `sr` with `from x in sr.DefaultIfEmpty()`. `DefaultIfEmpty` just initialises a default `Order` if the join yielded null. – Paul-Sebastian Manole Mar 02 '21 at 10:13
14
Public Sub LinqToSqlJoin07()
Dim q = From e In db.Employees _
        Group Join o In db.Orders On e Equals o.Employee Into ords = Group _
        From o In ords.DefaultIfEmpty _
        Select New With {e.FirstName, e.LastName, .Order = o}

ObjectDumper.Write(q) End Sub

Check http://msdn.microsoft.com/en-us/vbasic/bb737929.aspx

5

I found 1 solution. if want to translate this kind of SQL (left join) into Linq Entity...

SQL:

SELECT * FROM [JOBBOOKING] AS [t0]
LEFT OUTER JOIN [REFTABLE] AS [t1] ON ([t0].[trxtype] = [t1].[code])
                                  AND ([t1]. [reftype] = "TRX")

LINQ:

from job in JOBBOOKINGs
join r in (from r1 in REFTABLEs where r1.Reftype=="TRX" select r1) 
          on job.Trxtype equals r.Code into join1
from j in join1.DefaultIfEmpty()
select new
{
   //cols...
}
John Saunders
  • 160,644
  • 26
  • 247
  • 397
mokth
  • 113
  • 2
  • 6
  • See [this comment](http://stackoverflow.com/questions/700523/linq-to-sql-left-outer-join#comment21835463_700580), Linq-to-SQL entities don't support `DefaultIfEmpty`. – T.J. Crowder Jul 20 '15 at 08:22
2

I'd like to add one more thing. In LINQ to SQL if your DB is properly built and your tables are related through foreign key constraints, then you do not need to do a join at all.

Using LINQPad I created the following LINQ query:

//Querying from both the CustomerInfo table and OrderInfo table
from cust in CustomerInfo
where cust.CustomerID == 123456
select new {cust, cust.OrderInfo}

Which was translated to the (slightly truncated) query below

 -- Region Parameters
 DECLARE @p0 Int = 123456
-- EndRegion
SELECT [t0].[CustomerID], [t0].[AlternateCustomerID],  [t1].[OrderID], [t1].[OnlineOrderID], (
    SELECT COUNT(*)
    FROM [OrderInfo] AS [t2]
    WHERE [t2].[CustomerID] = [t0].[CustomerID]
    ) AS [value]
FROM [CustomerInfo] AS [t0]
LEFT OUTER JOIN [OrderInfo] AS [t1] ON [t1].[CustomerID] = [t0].[CustomerID]
WHERE [t0].[CustomerID] = @p0
ORDER BY [t0].[CustomerID], [t1].[OrderID]

Notice the LEFT OUTER JOIN above.

Brian Kraemer
  • 443
  • 3
  • 15
1

Take care of performance:

I experienced that at least with EF Core the different answers given here might result in different performance. I'm aware that the OP asked about Linq to SQL, but it seems to me that the same questions occur also with EF Core.

In a specific case I had to handle, the (syntactically nicer) suggestion by Marc Gravell resulted in left joins inside a cross apply -- similarly to what Mike U described -- which had the result that the estimated costs for this specific query were two times as high compared to a query with no cross joins. The server execution times differed by a factor of 3. [1]

The solution by Marc Gravell resulted in a query without cross joins.

Context: I essentially needed to perform two left joins on two tables each of which again required a join to another table. Furthermore, there I had to specify other where-conditions on the tables on which I needed to apply the left join. In addition, I had two inner joins on the main table.

Estimated operator costs:

  • with cross apply: 0.2534
  • without cross apply: 0.0991.

Server execution times in ms (queries executed 10 times; measured using SET STATISTICS TIME ON):

  • with cross apply: 5, 6, 6, 6, 6, 6, 6, 6, 6, 6
  • without cross apply: 2, 2, 2, 2, 2, 2, 2, 2, 2, 2

(The very first run was slower for both queries; seems that something is cached.)

Table sizes:

  • main table: 87 rows,
  • first table for left join: 179 rows;
  • second table for left join: 7 rows.

EF Core version: 2.2.1.

SQL Server version: MS SQL Server 2017 - 14... (on Windows 10).

All relevant tables had indexes on the primary keys only.

My conclusion: it's always recommended to look at the generated SQL since it can really differ.


[1] Interestingly enough, when setting the 'Client statistics' in MS SQL Server Management Studio on, I could see an opposite trend; namely that last run of the solution without cross apply took more than 1s. I suppose that something was going wrong here - maybe with my setup.