Search code examples
c#json.netasp.net-mvclinq

LINQ Union two lists that have same ID but different types


I am trying to merge two lists into one List without duplicates

JOIN operator returns only common elements

These are lists in JSON

List1 is:

{
    "screenID": 96,
    "create": true,
    "read": true,
    "update": true,
    "delete": true,
    "print": true
},
{
    "screenID": 97,
    "create": true,
    "read": true,
    "update": true,
    "delete": true,
    "print": true
},
{
    "screenID": 98,
    "create": true,
    "read": true,
    "update": true,
    "delete": true,
    "print": true
}

List2 is:

{
    "screenID": 96,
    "create": true,
    "read": true,
    "update": true,
    "delete": true,
    "print": false
},
{
    "screenID": 97,
    "create": true,
    "read": true,
    "update": true,
    "delete": true,
    "print": false
}

If ScreenID is same then I want to compare between CRUD elements like:

if(ScreenID == 96){
Create = List1.Create == true && List2.Create == false ? true : false
}

I tried this : var finalList = list1.Union(list2);

but the result was:

{
    "screenID": 96,
    "create": true,
    "read": true,
    "update": true,
    "delete": true,
    "print": true
},
{
    "screenID": 97,
    "create": true,
    "read": true,
    "update": true,
    "delete": true,
    "print": true
},
{
    "screenID": 98,
    "create": true,
    "read": true,
    "update": true,
    "delete": true,
    "print": true
},
{
    "screenID": 96,
    "create": true,
    "read": true,
    "update": true,
    "delete": true,
    "print": false
},
{
    "screenID": 97,
    "create": true,
    "read": true,
    "update": true,
    "delete": true,

I am beginner in LINQ so any help is appreciated

EDIT I am using .NET 3.1


Solution

  • EDIT: I've made some changes to adapt my original answer to .Net 3.5, this should now work provided you're working with C# 7.3 or later (Set by <LangVersion>7.3</LangVersion> in your .csproj file). I ditched the HashSet<T> for a Dictionary<T> which actually made the whole thing less verbose.

    While the exising answer is good I'd like to add an alternate approach using an extension method that essentially should do the same as UnionBy but accept an additonal Func<T, T, T> for handling duplicates.

    The UnionComparer makes it a bit verbose but that essentially just wraps the IEqualityComparer<TKey> for the key selector.

    public static IEnumerable<T> UnionBy<T, TKey>(
        this IEnumerable<T> first,
        IEnumerable<T> second,
        Func<T, TKey> keySelector,
        Func<T, T, T> duplicateHandler,
        IEqualityComparer<TKey> keyComparer = null)
    {
        if(keyComparer is null)
            keyComparer = EqualityComparer<TKey>.Default;
    
        var result = new Dictionary<TKey, T>(keyComparer);
    
        foreach (var item in first)
            result.Add(keySelector(item), item);
    
        foreach (var item in second)
        {
            var key = keySelector(item);
            if (result.TryGetValue(key, out var duplicate))
            {
                result[key] = duplicateHandler(item, duplicate);
            }
            else
            {
                result.Add(key, item);
            }
        }
        return result.Values;
    }
    

    I've made a simple example class for your data:

    class Operation
    {
        public int ScreenId { get; set; }
        public bool Create { get; set; }
    }
    

    Usage:

    var first = new List<Operation>()
    {
        new Operation () { ScreenId = 1, Create = false },
        new Operation () { ScreenId = 2, Create = false },
        new Operation () { ScreenId = 3, Create = false },
    };
                var second = new List<Operation>()
    {
        new Operation () { ScreenId = 3, Create = true },
        new Operation () { ScreenId = 4, Create = true },
        new Operation () { ScreenId = 5, Create = true },
    };
    
    var result = first.UnionBy(
        second,
        o => o.ScreenId,
        (a, b) => new Operation { ScreenId = a.ScreenId, Create = a.Create || b.Create });
    
    foreach (var operation in result)
        Console.WriteLine($"ID: {operation.ScreenId}, Create: {operation.Create}");
    
    Console.ReadLine();
    

    Result

    EDIT: I just saw you are limited to .Net 3.5, I'm not entirely sure you will not have to make any changes to this but HashSet<T> and IEqualityComparer<T> should be supported