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

C# LINQ同表父子层级搜索 子项匹配未返回父项问题排查

原有代码逻辑问题排查
  • 匹配范围仅覆盖根节点和一级直接子项,二级及更深层级的子节点完全没有被纳入匹配逻辑,例如测试数据中Id=4的Q1属于二级子节点,原有逻辑根本不会关联到根节点P1。
  • 搜索过滤条件仅作用于根节点的Name字段,完全没有判断子节点名称是否命中关键词,哪怕是一级子节点名称匹配搜索词,也不会触发根节点的返回。
  • 无搜索条件时也执行全表交叉连接,产生大量无意义的笛卡尔积结果,存在不必要的性能损耗。
高性能实现方案

总数据量为数千条级别时,单次数据库拉取符合状态的全量数据+内存构建层级索引的方案性能最优,可避免多表连接、递归SQL、N+1查询带来的IO开销,整体耗时远低于纯数据库层复杂查询实现。
完整实现代码如下:

// 1. 仅执行1次数据库查询,拉取所有符合状态要求的数据,最小化数据库IO
var allValidProjects = _projectSetupRepository.Table
    .Where(p => p.Status == status)
    .ToList();

// 无数据直接返回空结果
if (!allValidProjects.Any())
{
    return new List<ProjectSetup>();
}

// 2. 内存构建索引,O(n)时间复杂度完成层级映射
var projectMap = allValidProjects.ToDictionary(p => p.Id);
// 存储每个节点对应的顶层根节点ID
var nodeRootMap = new Dictionary<int, int>();

foreach (var project in allValidProjects)
{
    if (nodeRootMap.ContainsKey(project.Id)) continue;
    
    var tracePath = new List<int> { project.Id };
    var currentParentId = project.ParentId;
    // 向上回溯查找根节点
    while (true)
    {
        // 命中已缓存的根节点映射,直接给路径上所有节点赋值
        if (currentParentId.HasValue && nodeRootMap.TryGetValue(currentParentId.Value, out var cachedRootId))
        {
            foreach (var nodeId in tracePath)
            {
                nodeRootMap[nodeId] = cachedRootId;
            }
            break;
        }
        // 已经回溯到顶层根节点
        if (!currentParentId.HasValue || !projectMap.ContainsKey(currentParentId.Value))
        {
            var rootId = tracePath.Last();
            foreach (var nodeId in tracePath)
            {
                nodeRootMap[nodeId] = rootId;
            }
            break;
        }
        // 继续向上查找
        tracePath.Add(currentParentId.Value);
        currentParentId = projectMap[currentParentId.Value].ParentId;
    }
}

// 3. 按搜索条件匹配结果
IEnumerable<ProjectSetup> queryResult;
if (string.IsNullOrWhiteSpace(search))
{
    // 无搜索关键词直接返回所有顶层根节点
    queryResult = allValidProjects.Where(p => p.ParentId == null);
}
else
{
    // 找到所有名称命中的节点,映射到对应根节点后去重
    var matchedRootIds = allValidProjects
        .Where(p => p.Name.Contains(search))
        .Select(p => nodeRootMap[p.Id])
        .ToHashSet();
    queryResult = allValidProjects.Where(p => p.ParentId == null && matchedRootIds.Contains(p.Id));
}

// 排序后返回最终结果
var finalResult = queryResult.OrderBy(p => p.Name).ToList();
方案性能说明
  • 数据库侧仅执行一次单表过滤查询,无多表连接、无递归CTE、无多次往返请求,数据库压力极小。
  • 内存计算全流程为线性时间复杂度,数千条数据的索引构建、匹配计算总耗时低于1毫秒,无性能瓶颈。
  • 支持任意深度的父子层级匹配,不存在原逻辑只能匹配一级子节点的问题,任意层级子项命中关键词都会正确返回对应的顶层根项目。
  • 全程无EF客户端评估陷阱,不会产生意外的全表查询或N+1查询问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 01:45:44