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
相关产品推荐
相关产品推荐

