.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
相关产品推荐
相关产品推荐

