对查询结果进行分组
分组是 LINQ 最强大的功能之一。
下面的示例演示如何以各种方式对数据进行分组:
- 按照单个属性。
- 按照字符串属性的首字母。
- 按照计算出的数值范围。
- 按照布尔谓词或其他表达式。
- 按照复合键。
此外,最后两个查询将它们的结果投影到一个新的匿名类型中,该类型仅包含学生的名字和姓氏。
//单个属性作为组键对源元素进行分组 var queryLastNames = from student in students group student by student.LastName into newGroup orderby newGroup.Key select newGroup; 通过使用除对象属性以外的某个项作为组键对源元素进行分组, 在此示例中,键是学生姓氏的第一个字母 var queryFirstLetters = from student in students group student by student.LastName[0]; /* 通过使用某个数值范围作为组键对源元素进行分组。 然后,查询将结果投影到一个匿名类型中,该类型仅包含学生的名字和姓氏以及该学生所属的百分点范围。 使用匿名类型的原因是没有必要使用完整的 Student 对象来显示结果。*/ Helper method, used in GroupByRange. protected static int GetPercentile(Student s) { double avg = s.ExamScores.Average(); return avg > 0 ? (int)avg / 10 : 0; } var queryNumericRange = from student in students let percentile = GetPercentile(student) group new { student.FirstName, student.LastName } by percentile into percentGroup orderby percentGroup.Key select percentGroup; //通过使用布尔比较表达式对源元素进行分组 var queryGroupByAverages = from student in students group new { student.FirstName, student.LastName } by student.ExamScores.Average() > 75 into studentGroup select studentGroup; /* 下面的示例演示如何使用匿名类型来封装包含多个值的键。 在此示例中,第一个键值是学生姓氏的第一个字母。 第二个键值是一个布尔值,它指定该学生在第一次考试中的得分是否超过了 85。 可以按照该键中的任何属性对多组值进行排序。*/ var queryHighScoreGroups = from student in students group student by new { FirstLetter = student.LastName[0], Score = student.ExamScores[0] > 85 } into studentGroup orderby studentGroup.Key.FirstLetter select studentGroup;
创建嵌套分组
public void QueryNestedGroups() { var queryNestedGroups = from student in students group student by student.Year into newGroup1 from newGroup2 in (from student in newGroup1 group student by student.LastName) group newGroup2 by newGroup1.Key; // Three nested foreach loops are required to iterate // over all elements of a grouped group. Hover the mouse // cursor over the iteration variables to see their actual type. foreach (var outerGroup in queryNestedGroups) { Console.WriteLine("DataClass.Student Level = {0}", outerGroup.Key); foreach (var innerGroup in outerGroup) { Console.WriteLine(" Names that begin with: {0}", innerGroup.Key); foreach (var innerGroupElement in innerGroup) { Console.WriteLine(" {0} {1}", innerGroupElement.LastName, innerGroupElement.FirstName); } } } }
分组数据类型IGrouping接口:
namespace System.Linq
{
public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
{
TKey Key { get; }
}
public interface ILookup<TKey, TElement> : IEnumerable<IGrouping<TKey, TElement>>, IEnumerable
{
IEnumerable<TElement> this[TKey key] { get; }
int Count { get; }
bool Contains(TKey key);
}
}
对分组操作执行子查询
创建一个查询,以便将源数据排序到不同的组中,然后分别对每个组执行子查询。
每个示例中的基本技术都是使用一个名为 studentGroup 的“延续”对源元素进行分组,然后生成一个针对 studentGroup的新的子查询。 此子查询是针对外部查询所创建的每个新组运行的。
请注意,在此特定示例中,最终的输出不是组,而是匿名类型的平面序列,代码如下:
public void QueryMax() { var queryGroupMax = from student in students group student by student.Year into studentGroup select new { Level = studentGroup.Key, HighestScore = (from student2 in studentGroup select student2.ExamScores.Average()).Max() }; int count = queryGroupMax.Count(); Console.WriteLine("Number of groups = {0}", count); foreach (var item in queryGroupMax) { Console.WriteLine(" {0} Highest Score={1}", item.Level, item.HighestScore); } }
参考
[1]MSDN,对查询结果进行分组