Home > Blockchain >  Linq group by Titem a list of Ditem which each contains a collection of Titem
Linq group by Titem a list of Ditem which each contains a collection of Titem

Time:01-08

I have a collection of devices. For each device of this list, I have an inner list of zones. I want to group each device by zone, but I don't know how to do that in Linq, I want to get an IEnumerable<IGrouping<string, DeviceModel>> as output.

The string if the zonename in my ZoneModel.

My device class:

public class DeviceModel
{
        public List<ZoneModel> Zones { get; set; }
        public string Serial { get; set; }
}

My Zone class:

public class ZoneModel
{
        public string ZoneName { get; set; }
}

Now, I need to get a enumerable of group device by zone with this code:

List<DeviceModel> deviceList = new List<DeviceModel>();

... (device filled by some code)

IEnumerable<IGrouping<string, DeviceModel>> deviceByZoneGroups =
        from dataInfo in listDatas.Where(d => d.Zones != null && d.Zone.Any())
        group dataInfo by dataInfo.Zones into zoneGroup
        orderby zoneGroup.Key
        select zoneGroup;

CodePudding user response:

It looks like you want to group objects based on unique items in a property list.

This solution 1st uses a SelectMany overload that ties back the original object to the expanded property list to achieve the grouping you are asking for. It flattens out the lists of Zones that belongs to each Device in one list. But with the overload, it maintains the relationship to it's original parent object. It basically reverses the relationship from Device with many Zones to Zone with many Devices.

Then 2nd it uses an overload of GroupBy which allows you to map each result of the grouping, so that only a list of Device objects is returned. This maps IGrouping<string, anonymous type<string, DeviceModel>> to IGrouping<string, DeviceModel> which is what you asked for.

This should be your complete answer:

void Main()
{
    // Setup some test data
    var devices = GetTestData();

    // The main solution
    var group = devices
        .SelectMany(d => d.Zones, (device, zone) =>
            new
            {
                ZoneName = zone.ZoneName,
                Device = device
            })
        .GroupBy(d => d.ZoneName, x => x.Device);

    // Just for visibility
    group
        .Select(d => $"{d.Key}::{(string.Join(",", d.Select(x => x.Serial)))}")
        .ToList()
        .ForEach(x => Console.WriteLine(x));

    // This returns:
    //      Living Room::12,34
    //      Entryway::12,56
}

public class DeviceModel
{
    public List<ZoneModel> Zones { get; set; }
    public string Serial { get; set; }
}

public class ZoneModel
{
    public string ZoneName { get; set; }
}

public static List<DeviceModel> GetTestData()
{
    return
        new List<DeviceModel>
        {
            new DeviceModel
            {
                Serial = "12",
                Zones = new List<ZoneModel>
                    {
                        new ZoneModel { ZoneName = "Living Room" },
                        new ZoneModel { ZoneName = "Entryway" },
                    }
            },
            new DeviceModel
            {
                Serial = "34",
                Zones = new List<ZoneModel>
                    {
                        new ZoneModel { ZoneName = "Living Room" }
                    }
            },
            new DeviceModel
            {
                Serial = "56",
                Zones = new List<ZoneModel>
                    {
                        new ZoneModel { ZoneName = "Entryway" }
                    }

            }
        };
}

CodePudding user response:

You can use SelectMany and GroupBy

public static IEnumerable<IGrouping<string, DeviceModel>> GetGrouped(IEnumerable<DeviceModel> deviceModels)
    => deviceModels.SelectMany(i => i.Zones.Select(j => new { j.ZoneName, i })).GroupBy(i => i.ZoneName, i => i.i);
  • Related