Search code examples
c#linq.net-coreentity-framework-core

C# linq merge two collections by condition with "leading" collection


I'm trying to find a solution to an actually not so complicated problem but I didn't find it - maybe I've got the wrong keywords to look for, maybe I'm to tired to understand the docs correctly - whatever it is ... I'm stuck.

I have two collections of db-entities and I want to merge them by condition. If two items in both collections match condition, I want to take the one from myCollection. Every other item should be added to mergeCollection. I've wrote a cumbersome foreach but there must be a better solution. Would be pretty happy if someone can tell me how to solve this in a short and elegant way.

The type doesn't really matter, could be any object. Something like:

class MyClass
{
    public Guid Id { get; set; }
    public string SomeValue { get; set; }
    public int AnotherValue { get; set; }
}

This is my foreach:

void Merge(List<MyClass> myCollection)
{
    var mergeCollection = await dbContext.MyClass.ToListAsync();

    foreach (var updatedItem in myCollection)
    {
        if (updatedItem.SomeValue == default && updatedItem.AnotherValue == default)
            mergeCollection.Add(updatedItem);
        else
        {
            var oldItem = mergeCollection.First(e => e.Id == updatedItem.Id);
            mergeCollection.Remove(oldItem);
            mergeCollection.Add(updatedItem);
        }
    }
}

Solution

  • You want to modify collections, a loop is perfect for this. LINQ is a "tool" to query collections, not to modify them. So in my opionion you are asking for the wrong tool and you're using already the right one. However, maybe you find this approach more readable (i prefer yours):

    void Merge(List<MyClass> myCollection)
    {
        HashSet<Guid> myIds = myCollection.Select(i => i.Id).ToHashSet();
        List<MyClass> mergeCollection = await dbContext.MyClass.Where(i => !myIds.Contains(i.Id)).ToListAsync();
        mergeCollection.RemoveAll(i => myCollection
            .Any(i2 => i.Id == i2.Id && (i2.SomeValue != default || i2.AnotherValue != default)));
        mergeCollection.AddRange(myCollection);
    }
    

    This might be more efficient since i can use List<T>.AddRange(otherList).

    Since you've clarified that those items which are in "default-state" don't have the same ID's than those in mergeCollection, you can make it even more efficient:

    void Merge(List<MyClass> myCollection)
    {
       HashSet<Guid> myIds = myCollection.Select(i => i.Id).ToHashSet();
       List<MyClass> mergeCollection = await dbContext.MyClass
          .Where(i => !myIds.Contains(i.Id))
          .ToListAsync();
       mergeCollection.AddRange(myCollection);
    }