左连接两个列表并使用 Linq 从右侧维护一个属性

Posted

技术标签:

【中文标题】左连接两个列表并使用 Linq 从右侧维护一个属性【英文标题】:Left join on two Lists and maintain one property from the right with Linq 【发布时间】:2016-03-01 21:26:59 【问题描述】:

我有 2 个相同类型的列表。左侧列表:

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);

以及正确的列表:

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);

我想做一个左连接,但使用Changed属性值。像这样:

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

为此,我不能使用简单的Left Join,也不能使用Concat with GroupBy。

我怎样才能用 linq 做到这一点?谢谢。

【问题讨论】:

【参考方案1】:

这看起来像是一个非常标准的左外连接场景。

我总是将此扩展方法方便地用于左外连接,因此我不必查找如何使用讨厌的查询语法(或记住 GroupJoin 是什么)...

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));
    

现在你可以:

leftList.LeftOuterJoin(
     rightList, 
     lft => lft.Id,
     rgt => rgt.Id,
     (lft, rgt) => new PersonId = lft.Id, 
                              Name = lft.Name, 
                              Changed = rgt == null ? lft.Changed : rgt.Changed)

【讨论】:

你会在最后一行得到一个 nullref。更改后应分配rgt == null ? lft.Changed : rgt.Changed @CameronMacFarland:Ta! 您好,感谢您的帮助,抱歉耽搁了,我在这里很忙。不错的扩展,谢谢分享! @RenanAraújo 哈哈!没关系。 Resharper 几乎是为我写的!【参考方案2】:

为什么不试试这样的解决方案:

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();

【讨论】:

【参考方案3】:

spender 比我快,我没有任何扩展方法。

没有任何扩展方法:

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();

GroupJoin 进行左外连接操作。

【讨论】:

【参考方案4】:

另一种方法是:

//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 ;

【讨论】:

以上是关于左连接两个列表并使用 Linq 从右侧维护一个属性的主要内容,如果未能解决你的问题,请参考以下文章

使用 LINQ 获取两个比较列表的结果并根据结果更改列表中的属性

两个List属性,无法使用LINQ创建列表

如何使用 LINQ 从对象列表中获取唯一的属性列表?

结合列表中实体的两个属性并将其与 Linq 展平

Linq:选择不同的对象左连接

使用 Linq 合并两个列表,并根据需要从不同的表中添加数据