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

.NET Core中单表三级嵌套数据的高效查询方案咨询

嗨,这个问题我太懂了!你现在的写法属于典型的N+1查询问题——每次循环都要和数据库打一次交道,数据量稍微大一点性能直接拉胯。给你几个高效的解决方案,从易到难,你可以根据自己的情况选:

方案1:内存中构建嵌套结构(最推荐,简单高效)

先一次性把所有数据从数据库拉到内存里,然后用字典快速查找父节点,在内存里组装嵌套结构。全程只需要1次数据库查询,性能提升特别明显。

步骤很清晰:

  • 先查询所有Step数据,转成列表
  • 用字典建立「ID→Step实体」的映射,这样找父节点的时候O(1)就能定位
  • 遍历每个Step,根据parentId找到父节点,把自己加到父节点的子集合里
  • 最后筛选出根节点(parentId为0或null,看你数据库里根节点的定义)就是最终的嵌套结构

代码示例:

// 1. 一次性拉取所有数据到内存,只查一次数据库
var allSteps = _apiDbContext.StepResult.ToList();

// 2. 构建ID到Step的字典映射,快速查找父节点
var stepLookup = allSteps.ToDictionary(step => step.Id);

// 3. 初始化根节点列表
var rootSteps = new List<StepResult>();

foreach (var step in allSteps)
{
    if (step.StepParent == 0 || step.StepParent == null)
    {
        // 根节点直接加入结果列表
        rootSteps.Add(step);
    }
    else
    {
        // 找到对应的父节点,把当前节点加入子集合
        if (stepLookup.TryGetValue(step.StepParent.Value, out var parentStep))
        {
            // 先确保子集合不为null,避免空引用异常
            parentStep.Childs ??= new List<StepResult>();
            parentStep.Childs.Add(step);
        }
    }
}

// rootSteps就是你要的三级嵌套结构,每个节点都包含对应的子节点

注意:你的StepResult实体需要加一个Childs属性来存储子节点,比如:

public class StepResult
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? StepParent { get; set; }
    // 新增子节点集合属性
    public ICollection<StepResult> Childs { get; set; }
}
方案2:用EF Core导航属性+Include/ThenInclude

如果你确定层级固定是三级,可以直接用EF Core的导航属性,通过Include和ThenInclude一次性加载所有层级的数据,EF会自动生成JOIN查询,避免N+1。

首先要在DbContext里配置父子关系:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<StepResult>()
        .HasMany(s => s.Childs) // 一个父节点有多个子节点
        .WithOne() // 子节点对应一个父节点(不需要显式父节点属性,因为已经有StepParent)
        .HasForeignKey(s => s.StepParent) // 外键是StepParent
        .OnDelete(DeleteBehavior.Restrict); // 防止删除父节点时误删子节点,根据需求调整
}

然后查询的时候直接加载所有层级:

var rootSteps = _apiDbContext.StepResult
    .Where(s => s.StepParent == 0 || s.StepParent == null)
    .Include(s => s.Childs) // 加载一级子节点
        .ThenInclude(child => child.Childs) // 加载二级子节点(也就是你的第三级)
    .ToList();

这个方案的好处是EF帮你处理查询逻辑,但缺点是如果以后层级变多(比如四级),就得再加ThenInclude,不如方案1灵活。

方案3:递归CTE查询(适合超大数据量)

如果你的数据量特别大,不想一次性拉所有数据到内存,可以用SQL的递归CTE先查询出所有层级的数据,再在内存里组装。不过一般来说方案1已经够用了,这个属于进阶玩法。

比如SQL Server的CTE语句:

WITH RecursiveSteps AS (
    -- 根节点
    SELECT Id, Name, StepParent, 0 AS Level
    FROM StepResult
    WHERE StepParent IS NULL OR StepParent = 0
    UNION ALL
    -- 递归查询子节点
    SELECT s.Id, s.Name, s.StepParent, rs.Level + 1
    FROM StepResult s
    INNER JOIN RecursiveSteps rs ON s.StepParent = rs.Id
)
SELECT * FROM RecursiveSteps ORDER BY Level, Id;

然后在.NET里执行这个SQL,再用方案1的方式组装嵌套结构:

var recursiveSteps = _apiDbContext.StepResult
    .FromSqlRaw(@"WITH RecursiveSteps AS (
        SELECT Id, Name, StepParent, 0 AS Level
        FROM StepResult
        WHERE StepParent IS NULL OR StepParent = 0
        UNION ALL
        SELECT s.Id, s.Name, s.StepParent, rs.Level + 1
        FROM StepResult s
        INNER JOIN RecursiveSteps rs ON s.StepParent = rs.Id
    )
    SELECT * FROM RecursiveSteps ORDER BY Level, Id")
    .ToList();

// 接下来用方案1的字典映射方法组装嵌套结构即可

总结一下,方案1是最推荐的——实现简单,性能拉满,不管层级多少都能处理,完全解决你现在的性能问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:24:08