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

LINQ to SQL中复用Join结果及查询返回多余记录问题排查

问题分析与修复方案

你遇到的问题根源

你第二次修改后的代码之所以返回6条记录(而不是预期的2条List记录),是因为你在第一个join的分组结果后加了from project_task in project_task_result.DefaultIfEmpty()——这一步把List和它的Task做了笛卡尔展开:List A有4个Task,就会生成4条对应记录;List B有2个Task,生成2条,总共6条。虽然你最后用的是整个分组的Sum,但外层查询是基于展开后的每条Task记录生成的,所以结果条数就变成了Task的总数。

而第一次的语法错误,是因为你试图直接用project_task.TaskId去关联TimeLog,但此时project_task是第一个join的分组集合(IEnumerable<ProjectTask>),不是单个Task对象,自然无法直接访问TaskId属性,所以编译器报错。

正确的实现思路

我们要保证查询始终以List为核心,所有聚合操作都在List的上下文里完成,不要展开Task层级。这里提供两种可行的方案:

方案1:嵌套查询计算TimeLog总和

这种方式直接在List的投影中,针对每个Task去计算对应的TimeLog总和,再汇总:

var objs = from project_task_list in modelRepository.Get()
           where project_task_list.ProjectId == project_id
           // 关联Task并分组,保留List和它的所有Task的关联
           join project_task in projectTaskRepository.Get() 
               on project_task_list.ListId equals project_task.ListId into project_task_result
           select new ProjectTaskListModel 
           {
               ListId = project_task_list.ListId,
               ProjectId = project_task_list.ProjectId ?? 0,
               ListName = project_task_list.ListName,
               ListStartDate = project_task_list.ListStartDate,
               ListEndDate = project_task_list.ListEndDate,
               ListStatusId = project_task_list.ListStatusId ?? 0,
               ListSortOrder = project_task_list.ListSortOrder ?? 0,
               // 计算当前List下所有Task的预估工时总和
               ListTasksEstimatedHours = project_task_result.Sum(x => x.TaskEstimatedHours) ?? 0,
               // 先计算每个Task的TimeLog总和,再把所有Task的结果相加
               ListTasksTimeLogTotalMinutes = project_task_result
                   .Sum(task => 
                       projectTaskTimeLogRepository.Get()
                           .Where(timelog => timelog.TaskId == task.TaskId)
                           .Sum(timelog => timelog.LogMinutes) ?? 0) ?? 0
           };

方案2:预聚合TimeLog数据(性能更优)

如果TimeLog数据量较大,方案1可能会多次查询TimeLog表,此时可以先预计算每个Task的总日志分钟数,再关联查询:

// 先一次性计算所有Task的TimeLog总和,避免重复查询
var taskTimeLogSummary = from timelog in projectTaskTimeLogRepository.Get()
                         group timelog by timelog.TaskId into taskGroup
                         select new 
                         {
                             TaskId = taskGroup.Key,
                             TotalLogMinutes = taskGroup.Sum(x => x.LogMinutes) ?? 0
                         };

// 关联List、Task和预聚合的TimeLog数据
var objs = from project_task_list in modelRepository.Get()
           where project_task_list.ProjectId == project_id
           join project_task in projectTaskRepository.Get() 
               on project_task_list.ListId equals project_task.ListId into project_task_result
           select new ProjectTaskListModel 
           {
               ListId = project_task_list.ListId,
               ProjectId = project_task_list.ProjectId ?? 0,
               ListName = project_task_list.ListName,
               ListStartDate = project_task_list.ListStartDate,
               ListEndDate = project_task_list.ListEndDate,
               ListStatusId = project_task_list.ListStatusId ?? 0,
               ListSortOrder = project_task_list.ListSortOrder ?? 0,
               ListTasksEstimatedHours = project_task_result.Sum(x => x.TaskEstimatedHours) ?? 0,
               // 关联预聚合的Task日志数据,再求和
               ListTasksTimeLogTotalMinutes = project_task_result
                   .Join(taskTimeLogSummary, 
                         task => task.TaskId, 
                         summary => summary.TaskId, 
                         (task, summary) => summary.TotalLogMinutes)
                   .Sum() ?? 0
           };

效果说明

这两种方案都不会展开Task层级,始终以List作为查询的顶层对象,所以最终返回的记录数和List表的实际记录数一致(2条),同时正确计算了每个List对应的总预估工时和总日志分钟数。

内容的提问来源于stack exchange,提问作者Zain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:48:42