106

How do I change this query so it returns all u.usergroups?

from u in usergroups
from p in u.UsergroupPrices
select new UsergroupPricesList
{
UsergroupID = u.UsergroupID,
UsergroupName = u.UsergroupName,
Price = p.Price
};
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Lasse Edsvik
  • 9,070
  • 16
  • 73
  • 109
  • 1
    maybe [this](http://geekswithblogs.net/SudheersBlog/archive/2009/06/11/132758.aspx) can help. it was on another question here on [SO](http://stackoverflow.com/questions/2376701/linq-to-entities-how-to-define-left-join-for-grouping) – Menahem Apr 04 '11 at 12:30

7 Answers7

165

adapted from MSDN, how to left join using EF 4

var query = from u in usergroups
            join p in UsergroupPrices on u.UsergroupID equals p.UsergroupID into gj
            from x in gj.DefaultIfEmpty()
            select new { 
                UsergroupID = u.UsergroupID,
                UsergroupName = u.UsergroupName,
                Price = (x == null ? String.Empty : x.Price) 
            };
Oscar Acevedo
  • 1,144
  • 1
  • 12
  • 19
Menahem
  • 3,974
  • 1
  • 28
  • 43
  • 2
    I like this better than where gj.DefaultIfEmpty() at the end because I can use x in the where or select! – Gary Jan 31 '13 at 21:12
  • 1
    Can you explain the 'from x in gj.DefaultIfEmpty()' line? – Alex Dresko Jan 24 '14 at 13:42
  • @AlexDresko this part takes all the results from the join , and for the ones that have no right hand value , gives you null (default of object being null). hth – Menahem Jan 25 '14 at 23:53
  • Thanks @Menahem, but it seems like it would be null by default? What happens if you don't do the DefaultIfEmpty thing? Does it not do a left outer join? It seems like a really weird thing to have to do. – Alex Dresko Jan 26 '14 at 01:49
  • 1
    @AlexDresko , it's clunky i agree. i`m guessing this is because the group join operation isn't really an SQL like join , as it returns a hierarchical result rather than a flat table. – Menahem Jan 26 '14 at 13:17
  • @Menahem u equals p.UsergroupID is this correct ? I think we need u.columnName – rahularyansharma Jun 22 '15 at 12:26
  • what if price isn't a string but an int? – Djeroen Dec 03 '15 at 13:48
  • @AlexDresko its a bit late but without DefaultIfEmpty you end up with an inner join instead of a full outer join. – Mick Nov 06 '17 at 00:55
  • 2
    What if there are more than two tables? – MohammadHossein R Feb 09 '19 at 19:48
  • 2
    This changed slightly with efcore; `from x in gj.DefaultIfEmpty()` becomes `from p in gj.DefaultIfEmpty()`. https://learn.microsoft.com/en-us/ef/core/querying/complex-query-operators#left-join – carlin.scott May 15 '20 at 18:11
48

It might be a bit of an overkill, but I wrote an extension method, so you can do a LeftJoin using the Join syntax (at least in method call notation):

persons.LeftJoin(
    phoneNumbers,
    person => person.Id,
    phoneNumber => phoneNumber.PersonId,
    (person, phoneNumber) => new
        {
            Person = person,
            PhoneNumber = phoneNumber?.Number
        }
);

My code does nothing more than adding a GroupJoin and a SelectMany call to the current expression tree. Nevertheless, it looks pretty complicated because I have to build the expressions myself and modify the expression tree specified by the user in the resultSelector parameter to keep the whole tree translatable by LINQ-to-Entities.

public static class LeftJoinExtension
{
    public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
        this IQueryable<TOuter> outer,
        IQueryable<TInner> inner,
        Expression<Func<TOuter, TKey>> outerKeySelector,
        Expression<Func<TInner, TKey>> innerKeySelector,
        Expression<Func<TOuter, TInner, TResult>> resultSelector)
    {
        MethodInfo groupJoin = typeof (Queryable).GetMethods()
                                                 .Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] GroupJoin[TOuter,TInner,TKey,TResult](System.Linq.IQueryable`1[TOuter], System.Collections.Generic.IEnumerable`1[TInner], System.Linq.Expressions.Expression`1[System.Func`2[TOuter,TKey]], System.Linq.Expressions.Expression`1[System.Func`2[TInner,TKey]], System.Linq.Expressions.Expression`1[System.Func`3[TOuter,System.Collections.Generic.IEnumerable`1[TInner],TResult]])")
                                                 .MakeGenericMethod(typeof (TOuter), typeof (TInner), typeof (TKey), typeof (LeftJoinIntermediate<TOuter, TInner>));
        MethodInfo selectMany = typeof (Queryable).GetMethods()
                                                  .Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] SelectMany[TSource,TCollection,TResult](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,System.Collections.Generic.IEnumerable`1[TCollection]]], System.Linq.Expressions.Expression`1[System.Func`3[TSource,TCollection,TResult]])")
                                                  .MakeGenericMethod(typeof (LeftJoinIntermediate<TOuter, TInner>), typeof (TInner), typeof (TResult));

        var groupJoinResultSelector = (Expression<Func<TOuter, IEnumerable<TInner>, LeftJoinIntermediate<TOuter, TInner>>>)
                                      ((oneOuter, manyInners) => new LeftJoinIntermediate<TOuter, TInner> {OneOuter = oneOuter, ManyInners = manyInners});

        MethodCallExpression exprGroupJoin = Expression.Call(groupJoin, outer.Expression, inner.Expression, outerKeySelector, innerKeySelector, groupJoinResultSelector);

        var selectManyCollectionSelector = (Expression<Func<LeftJoinIntermediate<TOuter, TInner>, IEnumerable<TInner>>>)
                                           (t => t.ManyInners.DefaultIfEmpty());

        ParameterExpression paramUser = resultSelector.Parameters.First();

        ParameterExpression paramNew = Expression.Parameter(typeof (LeftJoinIntermediate<TOuter, TInner>), "t");
        MemberExpression propExpr = Expression.Property(paramNew, "OneOuter");

        LambdaExpression selectManyResultSelector = Expression.Lambda(new Replacer(paramUser, propExpr).Visit(resultSelector.Body), paramNew, resultSelector.Parameters.Skip(1).First());

        MethodCallExpression exprSelectMany = Expression.Call(selectMany, exprGroupJoin, selectManyCollectionSelector, selectManyResultSelector);

        return outer.Provider.CreateQuery<TResult>(exprSelectMany);
    }

    private class LeftJoinIntermediate<TOuter, TInner>
    {
        public TOuter OneOuter { get; set; }
        public IEnumerable<TInner> ManyInners { get; set; }
    }

    private class Replacer : ExpressionVisitor
    {
        private readonly ParameterExpression _oldParam;
        private readonly Expression _replacement;

        public Replacer(ParameterExpression oldParam, Expression replacement)
        {
            _oldParam = oldParam;
            _replacement = replacement;
        }

        public override Expression Visit(Expression exp)
        {
            if (exp == _oldParam)
            {
                return _replacement;
            }

            return base.Visit(exp);
        }
    }
}
fero
  • 6,050
  • 1
  • 33
  • 56
48

Please make your life easier (don't use join into group):

var query = from ug in UserGroups
            from ugp in UserGroupPrices.Where(x => x.UserGroupId == ug.Id).DefaultIfEmpty()
            select new 
            { 
                UserGroupID = ug.UserGroupID,
                UserGroupName = ug.UserGroupName,
                Price = ugp != null ? ugp.Price : 0 //this is to handle nulls as even when Price is non-nullable prop it may come as null from SQL (result of Left Outer Join)
            };
Tomasz Skomra
  • 569
  • 4
  • 7
  • 3
    Avoiding join into group is a matter of opinion, but it's certainly a valid opinion. `Price = ugp.Price` may fail if `Price` is a non-nullable property and the left join doesn't give any results though. –  Nov 06 '17 at 15:45
  • 4
    Agree with above, but with more than two tables this approach is so much easier to read and maintain. – Tomasz Skomra Nov 06 '17 at 15:49
  • 2
    We can check if `ugp == NULL` and set a default value for `Price`. – Hp93 Jul 10 '18 at 04:00
  • just perfect :) – MohammadHossein R Feb 09 '19 at 21:33
  • 3
    Awesome! I prefer this solution for readability. Also, this makes more joins (i.e. from 3 or more table) much easier! I used it successfully for 2 left joins (i.e. 3 tables). – Jeremy Morren May 27 '19 at 12:12
  • What if we want a list from right table ?? not a single value like price. – MOH3N Nov 23 '19 at 13:12
7

If you prefer method call notation, you can force a left join using SelectMany combined with DefaultIfEmpty. At least on Entity Framework 6 hitting SQL Server. For example:

using(var ctx = new MyDatabaseContext())
{
    var data = ctx
    .MyTable1
    .SelectMany(a => ctx.MyTable2
      .Where(b => b.Id2 == a.Id1)
      .DefaultIfEmpty()
      .Select(b => new
      {
        a.Id1,
        a.Col1,
        Col2 = b == null ? (int?) null : b.Col2,
      }));
}

(Note that MyTable2.Col2 is a column of type int). The generated SQL will look like this:

SELECT 
    [Extent1].[Id1] AS [Id1], 
    [Extent1].[Col1] AS [Col1], 
    CASE WHEN ([Extent2].[Col2] IS NULL) THEN CAST(NULL AS int) ELSE  CAST( [Extent2].[Col2] AS int) END AS [Col2]
    FROM  [dbo].[MyTable1] AS [Extent1]
    LEFT OUTER JOIN [dbo].[MyTable2] AS [Extent2] ON [Extent2].[Id2] = [Extent1].[Id1]
Diego
  • 18,035
  • 5
  • 62
  • 66
3

For 2 and more left joins (left joining creatorUser and initiatorUser )

IQueryable<CreateRequestModel> queryResult = from r in authContext.Requests
                                             join candidateUser in authContext.AuthUsers
                                             on r.CandidateId equals candidateUser.Id
                                             join creatorUser in authContext.AuthUsers
                                             on r.CreatorId equals creatorUser.Id into gj
                                             from x in gj.DefaultIfEmpty()
                                             join initiatorUser in authContext.AuthUsers
                                             on r.InitiatorId equals initiatorUser.Id into init
                                             from x1 in init.DefaultIfEmpty()

                                             where candidateUser.UserName.Equals(candidateUsername)
                                             select new CreateRequestModel
                                             {
                                                 UserName = candidateUser.UserName,
                                                 CreatorId = (x == null ? String.Empty : x.UserName),
                                                 InitiatorId = (x1 == null ? String.Empty : x1.UserName),
                                                 CandidateId = candidateUser.UserName
                                             };
Dmitrii Matunin
  • 275
  • 4
  • 5
2

I was able to do this by calling the DefaultIfEmpty() on the main model. This allowed me to left join on lazy loaded entities, seems more readable to me:

        var complaints = db.Complaints.DefaultIfEmpty()
            .Where(x => x.DateStage1Complete == null || x.DateStage2Complete == null)
            .OrderBy(x => x.DateEntered)
            .Select(x => new
            {
                ComplaintID = x.ComplaintID,
                CustomerName = x.Customer.Name,
                CustomerAddress = x.Customer.Address,
                MemberName = x.Member != null ? x.Member.Name: string.Empty,
                AllocationName = x.Allocation != null ? x.Allocation.Name: string.Empty,
                CategoryName = x.Category != null ? x.Category.Ssl_Name : string.Empty,
                Stage1Start = x.Stage1StartDate,
                Stage1Expiry = x.Stage1_ExpiryDate,
                Stage2Start = x.Stage2StartDate,
                Stage2Expiry = x.Stage2_ExpiryDate
            });
William Robinson
  • 307
  • 2
  • 13
  • 1
    Here, you don't need `.DefaultIfEmpty()` at all: it only affects what happens when `db.Complains` is empty. `db.Complains.Where(...).OrderBy(...).Select(x => new { ..., MemberName = x.Member != null ? x.Member.Name : string.Empty, ... })`, without any `.DefaultIfEmpty()`, would already perform a left join (assuming the `Member` property is marked as optional). –  Nov 06 '17 at 15:43
1

If UserGroups has a one to many relationship with UserGroupPrices table, then in EF, once the relationship is defined in code like:

//In UserGroups Model
public List<UserGroupPrices> UserGrpPriceList {get;set;}

//In UserGroupPrices model
public UserGroups UserGrps {get;set;}

You can pull the left joined result set by simply this:

var list = db.UserGroupDbSet.ToList();

assuming your DbSet for the left table is UserGroupDbSet, which will include the UserGrpPriceList, which is a list of all associated records from the right table.

Jaggan_j
  • 488
  • 1
  • 8
  • 9