简体   繁体   中英

LINQ - GroupBy Type

I have inherited some C# code. I do not fully understand how types work in the context of LINQ. I had some code that looked like this:

foreach (var item in Model.Items.GroupBy(i => i.ID))
{ DoStuff(item); }

I'm trying to update this code with a more complex grouping. I'm trying to group by both the ID and DepartmentID . In an attempt to do this, I have:

var items = Model.Items.GroupBy(x => new { i.ID, i.DepartmentID }).OrderBy(i => i.Name).ThenByDescending(i => i.OrderDate);
foreach (var item in items) 
{ DoStuff(item); }

When I compile my code, I get a compile-time error that says:

Argument 1: cannot convert from 'System.Linq.IGrouping<<anonymous type:string ID, int DepartmentID>, MyProject.Item>' to 'System.Linq.IGrouping<MyProject.Item>'

From this error, I can tell that I've basically built a new type when I did my two groupings. However, I can't figure out how to group my items by their ID and DepartmentID and then just return an System.Linq.IGrouping<MyProject.Item> . How do I do that?

In my experience, anonymous types get you nowhere with GroupBy . I would either create a new class, of it it's not worth that much effort, use a Tuple :

new Tuple.Create(i.ID, i.DepartmentID)

That's instead of the anonymous type you are now using:

new { i.ID, i.DepartmentID })

Then the i in subsequent chained LINQ methods will be a grouping not an Items object:

.OrderBy(i => i.Name) // this will not work

The i in this case will be an IGrouping<Tuple<int, int>, Model.Items> ; that is, it will be a key-values pair. You have to do you ordering as you loop through the IGrouping objects.

var groupings = Model.Items.GroupBy(x => new Tuple<int, int>(i.ID, i.DepartmentID));
foreach (var group in groupings)
{
    var groupKey = group.Key;
    group = group.OrderBy(i => i.Name).ThenByDescending(i => i.OrderDate);
    foreach (var groupedItem in group)
        DoStuff(groupKey, groupedItem);
}

Each grouping in the group is a list of items. Your .OrderBy(i => i.Name) call doesn't work because the objects you're trying to order are not Model.Items objects, but lists of Model.Items objects with keys. The keys are the anonymous objects you're creating with the ID and DepartmentID properties.

Generally with grouping, you'll have two nested loops, not just one. The outer loop loops through each grouping. The inner loop loops through each item in the grouping. You can do this:

foreach(var grouping in Model.Items
    .GroupBy(x => new { i.ID, i.DepartmentID })
    .OrderBy(b => b.Key.DepartmentID)
    .ThenBy(t => t.Key.ID))
{
    Console.WriteLine("{0}, {1}", grouping.Key.DepartmentID, grouping.Key.ID);
    foreach(var item in grouping.OrderBy(n => n.Name).ThenByDescending(i => i.OrderDate))
    {
        Console.WriteLine("\t{0}", item.Name)
    }
}

I added the OrderBy and ThenBy calls to help show how you work with both the grouping keys and the items within the groupings.

Your grouping is correct but your ordering is causing the problem.

After you have performed your grouping you have many lists of Item so ordering the groups by Name and OrderDate would only make sense if you grouped the items by thos fields too:

var items = Model.Items.GroupBy(x => new {
                            i.ID, 
                            i.DepartmentID,
                            i.Name,
                            i.OrderDate })
                       .OrderBy(i => i.Name)
                       .ThenByDescending(i => i.OrderDate);

This sounds like it may be changing functionality, and not what you are after. If you want each grouped list to be ordered by Name and OrderDate then you could either order the Item s before grouping, although I would be cautious that the ordering is preserved

var itemGroups = Model.Items.OrderBy(i => i.Name)
                       .ThenByDescending(i => i.OrderDate)
                       .GroupBy(x => new {
                            i.ID, 
                            i.DepartmentID,
                            i.Name,
                            i.OrderDate });

OR, preferably order each list when reading them out.

foreach(var itemGroup in itemGroups)
{
    var orderedgroup = itemGroup.OrderBy(i => i.Name)
                                .ThenByDescending(i => i.OrderDate)
}

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