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.