如何用LINQ实现带动态列的交叉表(Crosstab)转换?
动态行转列实现学生成绩交叉表
第一步:确保左外连接数据完整
首先要保证合并数据包含所有学生与所有考试类型的组合,无成绩项默认填0。LINQ左外连接写法如下(假设DTO包含基础字段):
// 假设DTO结构示例: // ClasslistDto: int StudentId, string StudentName // ExamTypeDto: int ExamTypeId, string ExamName // StudentMarkDto: int StudentId, int ExamTypeId, decimal Score var fullCombination = from student in classlistDtoList from examType in examTypeDtoList join mark in studentMarkDtoList on new { student.StudentId, examType.ExamTypeId } equals new { mark.StudentId, mark.ExamTypeId } into markGroup from mark in markGroup.DefaultIfEmpty() select new { student.StudentId, student.StudentName, examType.ExamName, Score = mark?.Score ?? 0 // 空成绩显示0 };
核心逻辑是先做学生与考试类型的笛卡尔积,再左外连接成绩表,确保不会遗漏任何学生-考试组合。
第二步:动态构建交叉表
由于考试类型数量不固定,硬编码Pivot方法无法适配,需手动分组并动态创建列,以下提供两种常用实现:
方式1:用ExpandoObject生成动态对象列表
适合代码中动态访问属性的场景:
// 获取所有唯一考试名称(作为交叉表列) var uniqueExamNames = fullCombination.Select(x => x.ExamName).Distinct().ToList(); // 按学生分组生成动态交叉表 var pivotResult = fullCombination .GroupBy(x => new { x.StudentId, x.StudentName }) .Select(group => { dynamic dynamicRow = new ExpandoObject(); var rowDict = (IDictionary<string, object>)dynamicRow; // 添加学生基础信息 rowDict["StudentId"] = group.Key.StudentId; rowDict["StudentName"] = group.Key.StudentName; // 为每个考试类型添加成绩列 foreach (var examName in uniqueExamNames) { var studentScore = group.FirstOrDefault(item => item.ExamName == examName)?.Score ?? 0; rowDict[examName] = studentScore; } return dynamicRow; }) .ToList();
方式2:用DataTable构建交叉表
适合绑定UI控件、导出Excel等场景:
var pivotTable = new DataTable(); // 添加学生基础列 pivotTable.Columns.Add("StudentId", typeof(int)); pivotTable.Columns.Add("StudentName", typeof(string)); // 获取所有唯一考试名称并添加为列 var uniqueExamNames = fullCombination.Select(x => x.ExamName).Distinct().ToList(); foreach (var examName in uniqueExamNames) { pivotTable.Columns.Add(examName, typeof(decimal)); // 按实际Score类型调整 } // 填充数据行 var studentGroups = fullCombination.GroupBy(x => new { x.StudentId, x.StudentName }); foreach (var group in studentGroups) { DataRow row = pivotTable.NewRow(); row["StudentId"] = group.Key.StudentId; row["StudentName"] = group.Key.StudentName; // 填充各考试成绩 foreach (var examName in uniqueExamNames) { var score = group.FirstOrDefault(item => item.ExamName == examName)?.Score ?? 0; row[examName] = score; } pivotTable.Rows.Add(row); }
常规Pivot函数失效原因
标准LINQ的Pivot实现依赖编译时已知的列名,而你的场景中考评类型动态可变,无法提前定义列,因此必须先收集所有列名,再动态构建每行数据。
内容的提问来源于stack exchange,提问作者Tonto
相关产品推荐
相关产品推荐

