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

如何优化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以内:

  1. 提取公共投影,消除重复子查询
    提前把工作流步骤6的状态/日期、最大步骤状态、步骤5年份这些重复用到的字段一次性投影出来,避免EF生成冗余子查询。
  2. 统一Concat两边查询结构,避免客户端评估
    确保Concat拼接的两个查询返回字段完全一致,让EF能翻译为服务端执行的UNION ALL语句,禁止在拼接前调用AsEnumerable()、ToList()等触发全量数据加载的方法。
  3. 补充对应索引降低数据库执行开销

优化后的参考实现:

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 12:18:14