You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 08:47:39