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

LINQ Include导致项目搜索性能下降,如何兼顾搜索与性能?

解决EF Core中Include关联实体导致的查询性能问题

嘿,我完全懂你现在的困扰——为了支持关联实体字段的搜索加上Include后,查询直接慢到3秒以上,但去掉Include又没法实现需求,对吧?咱们来一步步拆解问题,找到最优解。

问题还原

你原本实现的带搜索功能的DataGrid查询方法如下,添加Include后性能暴跌:

public async Task<IEnumerable<Project>> GetFilteredProjects(string searchString) 
{ 
    var projects = _context.Projects.Where(p => p.Current); 
    projects.Include(p => p.Client); 
    projects.Include(p => p.Architect); 
    projects.Include(p => p.ProjectManager); 

    if (!string.IsNullOrEmpty(searchString)) 
    { 
        projects = projects 
            .Where(p => p.NormalizedFullProjectName.Contains(searchString) || 
                        p.Client.NormalizedName.Contains(searchString) || 
                        p.Architect.NormalizedFullName.Contains(searchString) || 
                        p.ProjectManager.NormalizedFullName.Contains(searchString)); 
    } 

    projects = projects.OrderBy(p => p.Name).Take(10); 
    return await projects.ToListAsync(); 
}

而去掉Include后查询瞬间完成,但无法支持关联字段搜索:

public async Task<IEnumerable<Project>> GetFilteredProjects(string searchString) 
{ 
    var projects = _context.Projects.Where(p => p.Current); 

    if (!string.IsNullOrEmpty(searchString)) 
    { 
        projects = projects 
            .Where(p => p.Name.Contains(searchString)); 
    } 

    projects = projects.OrderBy(p => p.Name).Take(10); 
    return await projects.ToListAsync(); 
}

性能对比

  • 无Include时:
    无Include时性能图
  • 使用Include时:
    有Include时性能图

问题根源

为什么加了Include就变慢?因为EF Core默认会把所有Include的表通过JOIN合并成一个SQL查询,这会产生笛卡尔积——比如一个Project关联1个Client、1个Architect、1个ProjectManager,结果集的每条记录会包含所有这四个实体的字段,数据量瞬间膨胀;如果关联的实体更多,或者符合条件的Project数量较多,数据库需要传输和处理的数据量会呈倍数增长,直接拖慢查询速度。

解决方案

下面几个方案可以帮你在保留关联字段搜索和实体加载的同时,大幅提升性能:

1. 使用AsSplitQuery()拆分查询

这是最简单的优化方案,只需要在查询末尾加上AsSplitQuery(),让EF Core把多表JOIN拆分成多个独立的SQL查询:

public async Task<IEnumerable<Project>> GetFilteredProjects(string searchString) 
{ 
    var projects = _context.Projects.Where(p => p.Current); 

    if (!string.IsNullOrEmpty(searchString)) 
    { 
        projects = projects 
            .Where(p => p.NormalizedFullProjectName.Contains(searchString) || 
                        p.Client.NormalizedName.Contains(searchString) || 
                        p.Architect.NormalizedFullName.Contains(searchString) || 
                        p.ProjectManager.NormalizedFullName.Contains(searchString)); 
    } 

    projects = projects
        .OrderBy(p => p.Name)
        .Take(10)
        .Include(p => p.Client)
        .Include(p => p.Architect)
        .Include(p => p.ProjectManager)
        .AsSplitQuery(); // 关键:拆分查询

    return await projects.ToListAsync(); 
}

原理:EF Core会先查询符合条件的10条Project记录,然后分别查询这些Project对应的Client、Architect、ProjectManager数据,最后在内存中组装成完整的Project实体。这样每个查询的数据量都很小,避免了笛卡尔积导致的数据膨胀。

2. 投影到DTO(数据传输对象),只加载需要的字段

如果你不需要返回完整的Project实体,只需要展示DataGrid需要的字段,那么投影到DTO是更高效的选择——只查询和返回你真正需要的字段,避免加载冗余数据:

// 先定义对应的DTO类
public class ProjectDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string NormalizedFullProjectName { get; set; }
    public ClientDto Client { get; set; }
    public ArchitectDto Architect { get; set; }
    public ProjectManagerDto ProjectManager { get; set; }
}

public class ClientDto { public int Id { get; set; } public string NormalizedName { get; set; } }
public class ArchitectDto { public int Id { get; set; } public string NormalizedFullName { get; set; } }
public class ProjectManagerDto { public int Id { get; set; } public string NormalizedFullName { get; set; } }

// 修改查询方法
public async Task<IEnumerable<ProjectDto>> GetFilteredProjects(string searchString)
{
    var query = _context.Projects.Where(p => p.Current);

    if (!string.IsNullOrEmpty(searchString))
    {
        query = query.Where(p => 
            p.NormalizedFullProjectName.Contains(searchString) ||
            p.Client.NormalizedName.Contains(searchString) ||
            p.Architect.NormalizedFullName.Contains(searchString) ||
            p.ProjectManager.NormalizedFullName.Contains(searchString));
    }

    return await query
        .OrderBy(p => p.Name)
        .Take(10)
        .Select(p => new ProjectDto
        {
            Id = p.Id,
            Name = p.Name,
            NormalizedFullProjectName = p.NormalizedFullProjectName,
            Client = new ClientDto { Id = p.Client.Id, NormalizedName = p.Client.NormalizedName },
            Architect = new ArchitectDto { Id = p.Architect.Id, NormalizedFullName = p.Architect.NormalizedFullName },
            ProjectManager = new ProjectManagerDto { Id = p.ProjectManager.Id, NormalizedFullName = p.ProjectManager.NormalizedFullName }
        })
        .ToListAsync();
}

原理:EF Core会生成只包含所需字段的SQL查询,返回的数据量大幅减少;同时因为没有加载完整实体,也避免了笛卡尔积的问题,查询速度会显著提升。

3. 添加索引优化搜索性能

你的查询使用了Contains进行模糊搜索,默认情况下数据库无法利用普通索引优化这类查询。可以针对搜索字段添加全文索引(推荐)或者非聚集索引:

  • 全文索引:适用于任意位置的模糊搜索,比如SQL Server可以给Projects.NormalizedFullProjectName、Clients.NormalizedName等字段创建全文索引,这样Contains查询会使用全文索引,大幅提升搜索速度。
  • 非聚集索引:如果你的搜索是前缀匹配(比如StartsWith),可以给字段添加非聚集索引,但对于中间或后缀匹配的Contains,普通索引无法生效。

总结

优先尝试AsSplitQuery(),它不需要修改太多代码就能见效;如果DataGrid不需要完整实体,投影到DTO是更优的选择;最后配合索引优化搜索性能,就能完美解决你的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:20:01