如何使用LINQ在C#中按两个或更多字段对数据进行分组?
\n\n你好,朋友!如果你曾经用SQL编写过数据库查询,你一定记得带有多个列的GROUP BY结构。它允许你将数据拆分成逻辑分组——例如,先按城市分组销售数据,然后在每个城市内按商品类别分组。在C#中,同样的目的可以通过LINQ的GroupBy实现。但问题在于:标准的GroupBy只接受一个键。如果需要按多个列分组该怎么办?
别担心!解决方案既优雅又简单——我们创建一个匿名类型,将所需字段合并成一个复合键。在本文中,我不仅会展示基本语法,还会提供实际代码示例,你可以立即应用到自己的项目中。开始吧!
\n\n问题:一个键太少了
\n\n假设你有一个学生列表:
\n\npublic class Student\n{\n public string Name { get; set; }\n public string Course { get; set; }\n public int Grade { get; set; }\n}\n\nList<Student> students = new List<Student>\n{\n new Student { Name = "安娜", Course = "数学", Grade = 85 },\n new Student { Name = "鲍里斯", Course = "数学", Grade = 90 },\n new Student { Name = "安娜", Course = "物理", Grade = 78 },\n new Student { Name = "鲍里斯", Course = "物理", Grade = 82 },\n new Student { Name = "安娜", Course = "数学", Grade = 95 }\n};\n\n你想知道每个学生在每门课程中的平均成绩。如果只按姓名使用GroupBy,安娜的数学和物理数据会混在一起。如果只按课程分组,则会丢失与学生的关联。需要按两列分组:Name + Course。
解决方案:匿名类型作为复合键
\n\n在C#中,我们可以直接在现场创建一个包含所需字段的临时对象:
\n\nvar grouped = students\n .GroupBy(s => new { s.Name, s.Course })\n .Select(g => new\n {\n g.Key.Name,\n g.Key.Course,\n AverageGrade = g.Average(st => st.Grade)\n });\n\nforeach (var item in grouped)\n{\n Console.WriteLine($"{item.Name} | {item.Course} | 平均分: {item.AverageGrade:F2}");\n}\n\n输出:
\n安娜 | 数学 | 平均分: 90.00
\n鲍里斯 | 数学 | 平均分: 90.00
\n安娜 | 物理 | 平均分: 78.00
\n鲍里斯 | 物理 | 平均分: 82.00
请注意:new { s.Name, s.Course } 就是匿名类型。LINQ会自动根据对象的所有属性进行比较(值相等性)。
使用IGrouping和嵌套集合
\n\n有时你不需要聚合数据,而是希望保留组内的所有原始元素。这时可以使用IGrouping<TKey, TElement>接口。每个组包含一个键和一个元素集合:
var groups = students.GroupBy(s => new { s.Name, s.Course });\n\nforeach (var group in groups)\n{\n Console.WriteLine($"学生: {group.Key.Name}, 课程: {group.Key.Course}");\n foreach (var student in group)\n {\n Console.WriteLine($" 成绩: {student.Grade}");\n }\n}\n\n当你需要输出详细报告时,这种方法非常理想。
\n\n查询语法 vs 流畅语法——如何选择?
\n\nLINQ支持两种编写查询的风格。让我们看看两者:
\n\n1. 流畅语法(方法语法)
\nvar result = students\n .GroupBy(s => new { s.Name, s.Course })\n .Select(g => new { g.Key.Name, g.Key.Course, Count = g.Count() });\n\n2. 查询语法(类似SQL的表达式语法)
\nvar result = from student in students\n group student by new { student.Name, stud