使用LINQ合并集合,同时覆盖具有相同ID的行

3
我有两个对象集合。
例如:
List<Foo> firstFoos = new List<Foo>();
List<Foo> secondFoos = new List<Foo>();

firstFoos.Add(new Foo() { Id = 1, ValueA = 10, ValueB = 15 });
firstFoos.Add(new Foo() { Id = 2, ValueA = 20, ValueB = 25 });
firstFoos.Add(new Foo() { Id = 3, ValueA = 30, ValueB = 35 });
firstFoos.Add(new Foo() { Id = 4, ValueA = 40, ValueB = 45 });

secondFoos.Add(new Foo() { Id = 1, ValueA = 100, ValueB = 150 });
secondFoos.Add(new Foo() { Id = 2, ValueA = 200, ValueB = 250 });

使用LINQ,如何将具有相同ID的secondFoos覆盖firstFoos,从而合并这两个集合?
期望得到的结果是:
|   Id    | ValueA | ValueB |
|---------|--------|--------|
|    1    |   100  |   150  |
|    2    |   200  |   250  |
|    3    |   30   |   35   |
|    4    |   40   |   45   |

请注意,此示例案例仅有两个值列(ValueAValueB),但实际情况可能有更多。
7个回答

4

我会将其转换为一个 Id -> Foo 的词典,然后使用普通的 foreach 进行更新:

var fooDict = firstFoos.ToDictionary(foo => foo.Id, foo => foo);
foreach (var foo in secondFoos) 
    fooDict[foo.Id] = foo;
var newFoos = fooDict.Values.OrderBy(foo => foo.Id).ToList();

3
您可以定义自定义的相等比较器并使用Union()
public class FooComparer : IEqualityComparer<Foo>
{
    public bool Equals(Foo x, Foo y)
    {
        return x.Id == y.Id;
    }

    public int GetHashCode(Foo obj)
    {
        return obj.Id.GetHashCode();
    }
}

然后:
var mergedList = secondFoos.Union(firstFoos, new FooComparer())
                           .ToList();

这个方法利用了secondFoos中的项在任何firstFoo的项之前添加到结果枚举中,因此如果firstFoo中有具有已存在的Id的项,则会被过滤掉。当然,这假设Id应该在所有项中都是唯一的。


这是否保证返回 secondFoos 中的元素,当两者都存在时?另外,您的 GetHashCode 方法需要返回 obj.Id(或仅与 id 相关的哈希)。 - Random832
@Random832:这就是Linq to Objects目前的工作方式 - 如文档中所指定的那样,Union首先枚举第一个序列和第二个序列,然后按顺序产生尚未产生的每个元素。是的 - hashcode仅针对Id属性,因为这是此比较器唯一相关的属性。 - BrokenGlass
我敢打赌之前我看的时候它只有obj.GetHashCode。我看到那个声明,但是不确定它是否意味着它不能产生第二个集合[firstFoos]中对象的位置在第一个集合顺序中。我认为对我来说问题在于它没有足够明确地说明“每个元素”的含义,考虑到非默认等价关系。我不怀疑它目前是这样工作的,只是规范没有明确保证它总是会这样工作。 - Random832

1

这应该适用于您

var concat = firstFoos.Select(x => new { Foo = x, list=1 })
                      .Concat(secondFoos.Select(x => new { Foo = x, list= 2 });

var merge = from x in concat
            group x by x.Foo.Id into x
            select x.Count() == 1 ? x.First().Foo : x.First(y => y.list == 2).Foo;

1
var result = secondFoos.Concat(
    firstFoos.Except(secondFoos, 
    new LambdaComparer<Foo>((a, b) => a.Id == b.Id)))
    .ToList();

另一个选择,因为你永远不可能对同一个问题有太多的解决方案 ;)


0

另一个选项

var f1NotInF2 = from f1 in firstFoos
    where !secondFoos.Exists(f2 => f1.Id == f2.Id)
    select f1;
var mixed = f1NotInF2.Concat(secondFoos);

添加 OrderBy 将使答案完美。 - code4life

0

我会使用类似这样的东西:

            List<Foo> newFoos = new List<Foo>();
            Foo selected = null;
            foreach (Foo foo in firstFoos)
            {
                selected = secondFoos.FirstOrDefault(x => x.Id == foo.Id);
                if (selected != null)
                {                   
                    newFoos.Add(selected);                   
                }
                else
                {

                    newFoos.Add(foo);
                } 
            }

0

这个会有效:

var merged = firstFoos.Where(f => !secondFoos.Any(s => s.Id == f.Id))
    .Union(secondFoos).OrderBy(c=>c.Id);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接