Home > Software engineering >  LINQ Select IDs from multiple levels
LINQ Select IDs from multiple levels

Time:01-23

I'd like to get one list of IDs from all nested entities.

Code:

// Entities
class Floor
{
    public int Id { get; set; }
    public ICollection<Room> Rooms { get; set; } = new List<Room>();
}

class Room
{
    public int Id { get; set; }
    public ICollection<Chair> Chairs { get; set; } = new List<Chair>();
}
class Chair
{
    public int Id { get; set; }
}

// Setup
var floor = new Floor() { Id = 1000 };
var room = new Room() { Id = 100 };
var chair = new Chair() { Id = 10 };

room.Chairs.Add(chair);
floor.Rooms.Add(room);

var floors = new List<Floor>() { floor };

// Select all IDs
var ids = floors.???

Expected result:

{ 10, 100, 1000 }

What I've tried. It selects IDs only from the deepest level, not all of them:

// Select all IDs
var ids = floors
    .SelectMany(f => f.Rooms)
    .SelectMany(r => r.Chairs)
    .Select(ch => ch.Id)
    .ToList();

CodePudding user response:

SelectMany is what you need together with Append:

var ids = floors
    .SelectMany(f => f.Rooms
        .SelectMany(r => r.Chairs
            .Select(c => c.Id).Append(r.Id)).Append(f.Id));

CodePudding user response:

Your current code flattens the hierarchy to collection of Chair and selects only their ids.

With pure LINQ you can do via nesting SelectMany and using Append/Prepend:

var ids = floors
    .SelectMany(f => f.Rooms
        .SelectMany(r => r.Chairs
            .Select(ch => ch.Id) // select chairs ids
            .Append(r.Id)) // append "current" room id to it's collection of chair ids
        .Append(f.Id)) // append "current" floor id to it's collection of rooms and chairs ids
    .ToList();

CodePudding user response:

An ugly hack would be to append "fakes" to the child rooms/chairs with the ID of their parent:

var ids = floors
    .SelectMany(f => f.Rooms.Append(new Room { Id = f.Id }))
    .SelectMany(r => r.Chairs.Append(new Chair { Id = r.Id }))
    .Select(ch => ch.Id)
    .ToList();

CodePudding user response:

It can be done with a recursive helper function like this:

IEnumerable<int> CollectIds<T>(T x) => x switch {
    Floor f => f.Rooms.SelectMany(CollectIds).Prepend(f.Id),
    Room r => r.Chairs.SelectMany(CollectIds).Prepend(r.Id),
    Chair c => Enumerable.Repeat(c.Id, 1),
    _ => throw new NotSupportedException()
};

Usage:

var ids = floors
    .SelectMany(CollectIds)
    .ToList();

You could consider extracting this function into a common interface to avoid the awkward type switch and the recursion.

  • Related