如何优化C# Linq Concat示例 解决查询加载慢问题
性能问题根因
添加data = data.Concat(unconfirmed);后当年查询耗时陡增的核心原因有三点:
- 触发客户端评估,放大遍历开销:
unconfirmed的筛选逻辑基于complete集合(过滤条件为工作流步骤5对应日期年份小于查询年),和前序data集合(过滤条件为ResidencyConfirmationDate.Year == resultYear)的查询嵌套层级、字段投影结构不一致,EF Core无法将整个Concat逻辑翻译为单条UNION ALLSQL,会把unconfirmed关联的全量历史数据拉到内存执行分组、排序、过滤。查询当前年时,符合年份 < 当前年的历史记录量是往年的8~10倍,内存运算开销直接上涨。 - 重复子查询生成冗余CROSS APPLY:原代码在
Where条件中重复编写ApplicationActivityPhas.Where(xxx).Select(xxx).FirstOrDefault()逻辑,EF翻译时会为每处重复逻辑生成独立的交叉应用子查询,匹配记录数越多,数据库端子查询重复执行次数越高。 - 分组Top1查询缺索引触发全表排序:
unconfirmed中按AppNumber分组后取最新ResidencyConfirmationDate记录的逻辑,如果没有对应联合索引,数据库会执行全表排序,关联记录量越大排序开销越高。
优化方案
按以下顺序调整即可将查询耗时回落至500ms以内:
- 提取公共投影,消除重复子查询
提前把工作流步骤6的状态/日期、最大步骤状态、步骤5年份这些重复用到的字段一次性投影出来,避免EF生成冗余子查询。 - 统一Concat两边查询结构,避免客户端评估
确保Concat拼接的两个查询返回字段完全一致,让EF能翻译为服务端执行的UNION ALL语句,禁止在拼接前调用AsEnumerable()、ToList()等触发全量数据加载的方法。 - 补充对应索引降低数据库执行开销
优化后的参考实现:
var data = _db.ResidencyConfirmations.AsQueryable(); var year = Request.Form.GetValues("columns[11][search][value]")[0]; var showConfirmed = Request.Form.GetValues("columns[12][search][value]")[0]; int.TryParse(year, out var resultYear); data = data.Where(rc => rc.Application.ApplicationType.Label == "Home Rehab"); // 一次性投影所有需要用到的关联字段,消除重复子查询 var baseWithStageInfo = data.Select(rc => new { Confirmation = rc, Step6Stage = rc.Application.ApplicationActivityPhas .Where(aap => aap.ActivityPhas.WorkFlowStep == 6) .Select(aap => new { aap.ActivityPhas.ActivityPhase, aap.ActivityPhaseDate }) .FirstOrDefault(), MaxStepStage = rc.Application.ApplicationActivityPhas .Where(aap => aap.ActivityPhas.WorkFlowStep == rc.Application.ApplicationActivityPhas.Max(x => x.ActivityPhas.WorkFlowStep)) .Select(aap => aap.ActivityPhas.ActivityPhase) .FirstOrDefault(), Step5Year = rc.Application.ApplicationActivityPhas .Where(aap => aap.ActivityPhas.WorkFlowStep == 5) .Select(aap => aap.ActivityPhaseDate.Year) .FirstOrDefault() }); var allNotesReleased = baseWithStageInfo .Where(x => x.Step6Stage.ActivityPhase == "All Notes Released" && x.Step6Stage.ActivityPhaseDate.Year >= resultYear) .Select(x => x.Confirmation); var complete = baseWithStageInfo .Where(x => x.MaxStepStage == "Complete" && x.Step5Year < resultYear) .Select(x => x.Confirmation); var unconfirmed = complete .Where(rc => !rc.Application.ResidencyConfirmations .Any(x => x.ResidencyConfirmationDate.Year == resultYear)) .GroupBy(rc => rc.Application.AppNumber) .Select(g => g.OrderByDescending(x => x.ResidencyConfirmationDate).First()); data = allNotesReleased.Concat(complete) .Where(rc => rc.ResidencyConfirmationDate.Year == resultYear); if (showConfirmed == "false") data = data.Where(rc => !rc.Deed || !rc.Utility || !rc.VitalRecords); // 拼接后加去重,避免关联产生的重复记录 data = data.Concat(unconfirmed).Distinct();
可选索引优化
给对应表添加以下联合索引,可将查询耗时进一步压缩至100ms级别:
ResidencyConfirmations表:建索引(ResidencyConfirmationDate, ApplicationId),包含Deed, Utility, VitalRecords字段ApplicationActivityPhas表:建索引(ApplicationId, WorkFlowStep),包含ActivityPhase, ActivityPhaseDate字段Applications表:建索引(AppNumber, Id),包含ApplicationType.Label字段
可以通过EF Core的日志输出检查最终生成的SQL,如果Concat部分翻译为UNION ALL则说明服务端执行生效,若出现客户端评估警告,对应调整查询结构直到警告消失即可。
内容的提问来源于stack exchange,提问作者user17824052
相关产品推荐
相关产品推荐

