Home > Software design >  .NET: How to group by multiple properties?
.NET: How to group by multiple properties?

Time:03-22

Given the following flattened "Mapping" class with Properties "UserId" and "PermissionId".

Lets say I have a Set<Mapping> with 5 items:

UserId PermissionId
A X
A Y
B X
B Y
B Z

And I want it to be grouped for the UI like this (lets call it "Row" class with properties List<UserId> "UserIds" and List<PermissionId> "PermissionIds"):

UserIds PermissionIds
A, B X, Y
B Z

How would I group the Set<Mapping> to a List<Row>? I got no clue how to do it. I mean, is this more math or can it be done kind of easily by .NET?

I know it can be done very easily for the following outcome with GroupBy but I get no further:

UserIds PermissionIds
A X, Y
B X, Y, Z

Thank you in advance!

CodePudding user response:

One possible apporach is to group, create one object per group, and then regroup the new objects before creating your Row objects.

Seeing as you have used characters for UserId and PermissionId in your example, I am regarding those properties as chars in my example.

The idea is roughly:

  1. Order by UserId (not necessary in your specific example, but otherwise)
  2. Group by PermissionId
  3. Create one object per grouping, containing PermissionId and the collection of associated UserIds
  4. Group the objects by the string created by the UserId characters
  5. Create one Row object per grouping by
    • converting the group key (user ID string) to a list of chars
    • extracting all the PermissionIds from the group

It could be implemented as follows:

List<Row> rows = mappings
    .OrderBy(m => m.UserId)
    .GroupBy(m => m.PermissionId)
    .Select(gr => (
        PermissionId: gr.Key,
        UserIds: gr.Select(m => m.UserId).ToArray() ))
    .GroupBy(permissionForUsers => new string(permissionForUsers.UserIds),
        ( userIds, permissionsForUsers ) => new Row {
            UserIds = userIds.ToList(),
            PermissionIds = permissionsForUsers.Select(m => m.PermissionId).ToList() })
    .ToList();

Example fiddle here.


Suggestion for improving this implementation:
In the first .Select() operation, use a collection implementation for the UserIds object field that is comparable, so that the following .GroupBy() operation is not dependent on creating a string (for the group key) and then transforming the key back to a char collection again when creating the Row object's UserIds.

CodePudding user response:

Try grouping the data twice. First by PermissionId, then by the joined string of UserId (eg. 'X,Y'):

var groups = list.GroupBy(x => x.PermissionId);

var groups2 = groups.GroupBy(x => string.Join(',', x.Select(g => g.UserId)));

foreach (var group in groups2)
{
    Console.WriteLine(group.Key   " - "   string.Join(',', group.Select(g => g.Key)));   
}

Note: for this to work, the items need to be sorted by UserId and PermissionId.

  • Related