17

I have 2 lists of the same type. The left list:

var leftList = new List<Person>();
leftList.Add(new Person {Id = 1, Name = "John", Changed = false});
leftList.Add(new Person {Id = 2, Name = "Alice", Changed = false});
leftList.Add(new Person {Id = 3, Name = "Mike", Changed = false});

And the right list:

var rightList = new List<Person>();
rightList.Add(new Person {Id = 1, Name = "John", Changed = false});
rightList.Add(new Person {Id = 3, Name = "Mike", Changed = true});
rightList.Add(new Person {Id = 4, Name = "Joshi", Changed = true});

I want to do a left join, but using the value on the Changed property from the right. Like this:

{Id = 1, Name = "John", Changed = false}
{Id = 2, Name = "Alice", Changed = false}
{Id = 3, Name = "Mike", Changed = true} // <-- true from the rightList

For this, I can't use simple Left Join, and I cannot use a Concat with GroupBy.

How can I do this with linq? Thanks.

Community
  • 1
  • 1
Renan Araújo
  • 3,533
  • 11
  • 39
  • 49

4 Answers4

24

This looks like a pretty standard left outer join scenario.

I always keep this extension method handy for left outer joins so I don't have to look up how to use the nasty query syntax (or remember wtf a GroupJoin is)...

public static class LinqEx
{
    public static IEnumerable<TResult> LeftOuterJoin<TOuter, TInner, TKey, TResult>(
        this IEnumerable<TOuter> outer, 
        IEnumerable<TInner> inner, 
        Func<TOuter, TKey> outerKeySelector, 
        Func<TInner, TKey> innerKeySelector, 
        Func<TOuter, TInner, TResult> resultSelector)
    {
        return outer
            .GroupJoin(inner, outerKeySelector, innerKeySelector, (a, b) => new
            {
                a,
                b
            })
            .SelectMany(x => x.b.DefaultIfEmpty(), (x, b) => resultSelector(x.a, b));
    }
}

Now you can:

leftList.LeftOuterJoin(
     rightList, 
     lft => lft.Id,
     rgt => rgt.Id,
     (lft, rgt) => new Person{Id = lft.Id, 
                              Name = lft.Name, 
                              Changed = rgt == null ? lft.Changed : rgt.Changed})
spender
  • 117,338
  • 33
  • 229
  • 351
7

Why don't you try a solution like this:

var query = (from left in leftList
    join right in rightList on left.Id equals right.Id into joinedList
    from sub in joinedList.DefaultIfEmpty()
    select new Person { 
        Id = left.Id,
        Name = left.Name,
        Changed = sub == null ? left.Changed : sub.Changed }).ToList();
user449689
  • 3,142
  • 4
  • 19
  • 37
4

Well spender was faster then me, I did without any extension method.

Without any extension method:

List<Person> mergedList =  leftList
            .GroupJoin(
                rightList, left => left.Id, right => right.Id, 
                (x, y) => new { Left = x, Rights = y  }
            )
            .SelectMany(
                x => x.Rights.DefaultIfEmpty(),
                (x, y) => new Person
                {
                    Id = x.Left.Id, 
                    Name = x.Left.Name, 
                    Changed = y == null ? x.Left.Changed : y.Changed
                }
            ).ToList();

The GroupJoin makes left outer join operation.

Community
  • 1
  • 1
1

Another way to do it would be:

//Step 1: Merge the lists while selecting the matching objects from rightList using Last()
var mergedList = leftList.Concat(rightList)
 .GroupBy(x => x.Id)
 .Select(x => x.Last());

//Step 2: Do a inner join between mergedList and leftList to get a left join result as originally required.
var innerJoinQuery = from mPerson in mergedList
                     join leftPerson in leftList on mPerson.Id equals leftPerson.Id
                     select new { Id = leftPerson.Id, Name = mPerson.Name, Changed = mPerson.Changed };
Leo
  • 665
  • 2
  • 9
  • 25
Dev Shah
  • 302
  • 1
  • 7