简体   繁体   中英

SelectMany on a list of objects

I have a class:

public class User
{
        public Guid ObjectId { get; set; }        

        public List<Guid> Groups { get; set; }
}

public class Group
{        
        public List<User> Users { get; set; }
}


Example:

Users:
[
    {
        "ObjectId": "1",    
        "Groups": ["G1"]
    },
    {
        "ObjectId": "2",    
        "Groups": ["G2"]
    },
    {
        "ObjectId": "3",    
        "Groups": ["G3"]
    },
    {
        "ObjectId": "1",    
        "Groups": ["G4"]
    }
]

Note that ObjectId "1" is present 2 times (One with G1, One with G4)

On running

var source = Group.SelectMany(x => x.Users).ToList();

I see the output as:

[
    {
        "ObjectId": "1",    
        "Groups": ["G1"]
    },
    {
        "ObjectId": "2",    
        "Groups": ["G2"]
    },
    {
        "ObjectId": "3",    
        "Groups": ["G3"]
    }
]

How do I get the output as:

[
    {
        "ObjectId": "1",    
        "Groups": ["G1"]
    },
    {
        "ObjectId": "2",    
        "Groups": ["G2"]
    },
    {
        "ObjectId": "3",    
        "Groups": ["G3"]
    },
    {
        "ObjectId": "1",    
        "Groups": ["G4"]
    }
]

If the input is:

Users:
[
    {
        "ObjectId": "1",    
        "Groups": ["G1"]
    },
    {
        "ObjectId": "2",    
        "Groups": ["G2"]
    },
    {
        "ObjectId": "3",    
        "Groups": ["G3"]
    },
    {
        "ObjectId": "1",    
        "Groups": ["G1"]
    }
]

The output should be:

[
    {
        "ObjectId": "1",    
        "Groups": ["G1"]
    },
    {
        "ObjectId": "2",    
        "Groups": ["G2"]
    },
    {
        "ObjectId": "3",    
        "Groups": ["G3"]
    }
]

UPDATE:

Apologize as my question was not clear:

Classes:

public class GroupMembership
{
        public List<AzureADUser> SourceMembers { get; set; }
}


public class AzureADUser
{
    public Guid ObjectId { get; set; }  
    public List<Guid> SourceGroups { get; set; }
}

var users1 = new List<AzureADUser> {
    new () { ObjectId = new Guid("Guid1"), SourceGroups = new List<Guid> {new Guid("GuidG1")}},
    new () { ObjectId = new Guid("Guid2"), SourceGroups = new List<Guid> {new Guid("GuidG2")}},
    new () { ObjectId = new Guid("Guid3"), SourceGroups = new List<Guid> {new Guid("GuidG3")}},
    new () { ObjectId = new Guid("Guid1"), SourceGroups = new List<Guid> {new Guid("GuidG4")}} //include this
};


var users2 = new List<AzureADUser> {
    new () { ObjectId = new Guid("Guid1"), SourceGroups = new List<Guid> {<GuidG1>}} // remove this as this is a duplicate
};

var groupMembership1 = new GroupMembership
{
    SourceMembers = users1;
};

var groupMembership2 = new GroupMembership
{
    SourceMembers = users2;
};

var groupsMemberships = new List<GroupMembership>();
groupsMemberships.Add(groupMembership1);
groupsMemberships.Add(groupMembership2);
/* output:
    ObjectId: new Guid("Guid1"), SourceGroups: new Guid("Guid1")
    ObjectId: new Guid("Guid2"), SourceGroups: new Guid("Guid2")
    ObjectId: new Guid("Guid3"), SourceGroups: new Guid("Guid3")
    ObjectId: new Guid("Guid1"), SourceGroups: new Guid("GuidG4")
*/

SelectMany isn't the problematique API here, it just collects the existing sub-collections of Users into one. It's doesn't remove any and doesn't look at the data at all.

If your goal is to remove duplicates from your list, you could use LINQ's Distinct method on the result of SelectMany - and provide the appropriate IEqualityComparer that checks the User data for equaility. The comparison should return true if all values, ObjectID and all elements in Group, are equal.

Your code is somewhat confusing: Your JSON deals with ints and strings, but the class defs uses GUIDs. I'll use the JSON definition for the following example:

    public static void Main()
        {
            var users = new List<User> {
                new () { ObjectId = 1, Groups = new () {"G1"}},
                new () { ObjectId = 2, Groups = new () {"G2"}},
                new () { ObjectId = 3, Groups = new () {"G3"}},
                new () { ObjectId = 1, Groups = new () {"G4"}}, // should remain
                new () { ObjectId = 1, Groups = new () {"G1"}}, // should be removed equal to 1st
            };
            
            var distinct = users.Distinct(new UserComp());
            
            foreach (var usr in distinct)
                Console.WriteLine(usr);

                /* output:
                ID: 1, Groups: G1
                ID: 2, Groups: G2
                ID: 3, Groups: G3
                ID: 1, Groups: G4
                */
        }   
    
    public class UserComp : IEqualityComparer<User> {
        public bool Equals (User usr1, User usr2)
            {
                if (usr1 is null) return usr2 is null;
                if (usr2 is null) return false;
                return usr1.ObjectId == usr2.ObjectId && ((usr1.Groups is {} g1 && usr2.Groups is {} g2 && g1.SequenceEqual(g2)) || (usr1.Groups is null && usr2.Groups is null));
            }
    
            // hash is kind of weak ignoring the group data, but suitable for the demo
            public int GetHashCode (User usr) => usr?.ObjectId ?? 0;
    }
    
    public class User 
    {
            public int ObjectId { get; set; }        
    
            public List<string> Groups { get; set; }
            
            public override string ToString () => $"ID: {ObjectId}, Groups: {(Groups == null ? "-" : string.Join(", " ,Groups))}";
    }

Note that you don't need an explicit equaility comparer class if you put the same logic into the override of User.Equals .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM