user356178
user356178

Reputation:

Merging collections using LINQ while overriding rows with same ID

I got two collections of objects.

For example:

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

Using LINQ, how can I merge the two collection overriding firstFoos by secondFoos which have the same ID?

Expected result is:

|   Id    | ValueA | ValueB |
|---------|--------|--------|
|    1    |   100  |   150  |
|    2    |   200  |   250  |
|    3    |   30   |   35   |
|    4    |   40   |   45   |

Please note that this example case has only two value columns (ValueA and ValueB), but an actual case could have many more.

Upvotes: 3

Views: 2117

Answers (7)

code4life
code4life

Reputation: 15794

This will work:

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

Upvotes: 0

Matija Grcic
Matija Grcic

Reputation: 13391

I would use something like this:

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

Upvotes: 0

JotaBe
JotaBe

Reputation: 39055

Another option

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

Upvotes: 0

Servy
Servy

Reputation: 203825

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

Another option, because you can never have too many solutions to the same problem ;)

Upvotes: 1

Aducci
Aducci

Reputation: 26694

This should work for you

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;

Upvotes: 1

BrokenGlass
BrokenGlass

Reputation: 160992

You can define a custom equality comparer and use 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();
    }
}

And then:

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

This uses the fact that items in secondFoos are added to the resulting enumeration before any item in firstFoo, any item in firstFoo with an already existing Id will hence be filtered out. This assumes of course that Id should be distinct across all items.

Upvotes: 3

tzaman
tzaman

Reputation: 47840

I'd convert it to an Id -> Foo dictionary, and then just update with a regular 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();

Upvotes: 4

Related Questions