LINQ to SQL - Left Outer Join with multiple join conditions

Viewed 158674

I have the following SQL, which I am trying to translate to LINQ:

SELECT f.value
FROM period as p 
LEFT OUTER JOIN facts AS f ON p.id = f.periodid AND f.otherid = 17
WHERE p.companyid = 100

I have seen the typical implementation of the left outer join (ie. into x from y in x.DefaultIfEmpty() etc.) but am unsure how to introduce the other join condition (AND f.otherid = 17)

EDIT

Why is the AND f.otherid = 17 condition part of the JOIN instead of in the WHERE clause? Because f may not exist for some rows and I still want these rows to be included. If the condition is applied in the WHERE clause, after the JOIN - then I don't get the behaviour I want.

Unfortunately this:

from p in context.Periods
join f in context.Facts on p.id equals f.periodid into fg
from fgi in fg.DefaultIfEmpty()
where p.companyid == 100 && fgi.otherid == 17
select f.value

seems to be equivalent to this:

SELECT f.value
FROM period as p 
LEFT OUTER JOIN facts AS f ON p.id = f.periodid 
WHERE p.companyid = 100 AND f.otherid = 17

which is not quite what I'm after.

7 Answers

You need to introduce your join condition before calling DefaultIfEmpty(). I would just use extension method syntax:

from p in context.Periods
join f in context.Facts on p.id equals f.periodid into fg
from fgi in fg.Where(f => f.otherid == 17).DefaultIfEmpty()
where p.companyid == 100
select f.value

Or you could use a subquery:

from p in context.Periods
join f in context.Facts on p.id equals f.periodid into fg
from fgi in (from f in fg
             where f.otherid == 17
             select f).DefaultIfEmpty()
where p.companyid == 100
select f.value

this works too, ...if you have multiple column joins

from p in context.Periods
join f in context.Facts 
on new {
    id = p.periodid,
    p.otherid
} equals new {
    f.id,
    f.otherid
} into fg
from fgi in fg.DefaultIfEmpty()
where p.companyid == 100
select f.value

Can be written using composite join key. Also if there is need to select properties from both left and right sides the LINQ can be written as

var result = context.Periods
    .Where(p => p.companyid == 100)
    .GroupJoin(
        context.Facts,
        p => new {p.id, otherid = 17},
        f => new {id = f.periodid, f.otherid},
        (p, f) => new {p, f})
    .SelectMany(
        pf => pf.f.DefaultIfEmpty(),
        (pf, f) => new MyJoinEntity
        {
            Id = pf.p.id,
            Value = f.value,
            // and so on...
        });

Whilst my response below does not directly answer the question, I believe it offers an alternative to the core issue that a read might find valuable.

I end up at this thread and others looking for the EF equivalent of a simple self join SQL I had written. I included Entity Framework in my project to make my DB interactions easier, but having to use "GroupJoin" , "SelectMany" and "DefaultIfEmpty" is like having to translate to another language.

Additionally I work with engineer who are excellent at SQL but have limited C# skills. So I want a solution that they could read.

The solution that worked for me was:

context.Database.SqlQuery<Class>

This allows executing of SQL commands with result return in a typed object. So long as the column names returned match the property names of the given Class. For example:

 public class MeasurementEvent
{
    public int ID { get; set; }
    public string JobAssemID { get; set; }
    public DateTime? InspDate { get; set; }


}

var list = context.Database.SqlQuery<MeasurementEvent>(@"
                Select op.umeMeasurementEventID as ID, op.umeJobID+'.'+Cast(op.umeAssemblyID as varchar) as JobAssemID ,  insp.umeCreatedDate as InspDate 
                from uMeasurementEvents as op 
                    left JOIN   uMeasurementEvents as insp on op.umeJobID = insp.umeJobID and op.umeAssemblyID = insp.umeAssemblyID and insp.umeInstanceId = 1 and insp.umeIsInspector = 1
                  where  op.umeInstanceId = 1  and op.umeIsInspector = 0")
            .ToList();
Related