求助:如何让EF Core递归加载对象的全部祖辈与后代属性?
嘿,我太懂你这种焦头烂额的感觉了——你没操作错,EF Core默认的Include/ThenInclude确实只能处理固定层级的关联,没法自动递归加载所有祖辈或孙辈的Category,这是它的默认设计逻辑,不是你的问题😎
下面分两种场景给你具体的解决方案:
一、加载所有祖辈(向上遍历Parent链)
如果要把目标Category的所有父级、祖父级一直到根节点都加载出来,有两种常用方法:
方法1:用数据库递归CTE查询(高效推荐)
直接借助数据库的递归公共表表达式(CTE)来一次性查出完整的祖辈链,性能最优,适合层级深的场景:
// 先拿到目标Link对应的CategoryId var targetLink = FP.Links .Include(a => a.Category) .FirstOrDefault(a => a.TransId == _transId && /* 你的其他条件 */); if (targetLink?.Category == null) return; // 用CTE递归查询所有祖辈 var allAncestors = FP.Categories .FromSqlRaw(@" WITH RECURSIVE CategoryHierarchy AS ( -- 起始节点:目标Category SELECT Id, ParentId, Name FROM Categories WHERE Id = {0} UNION ALL -- 递归关联父节点 SELECT c.Id, c.ParentId, c.Name FROM Categories c JOIN CategoryHierarchy ch ON c.Id = ch.ParentId ) SELECT * FROM CategoryHierarchy ", targetLink.Category.Id) .ToList();
查出来的allAncestors就包含了目标Category本身以及所有往上的祖辈,你可以直接用这个集合,或者手动把祖辈链关联到targetLink.Category的Parent属性上。
方法2:手动递归加载(适合层级较浅的场景)
如果你的Category层级不多(比如最多3-4层),可以在内存里手动循环加载父节点,虽然会产生多次数据库查询,但实现简单:
var link = FP.Links .Include(a => a.Category) .FirstOrDefault(a => a.TransId == _transId && /* 你的其他条件 */); if (link?.Category != null) { var currentCat = link.Category; // 循环加载父节点直到根节点 while (currentCat.ParentId != null) { currentCat.Parent = FP.Categories.Find(currentCat.ParentId); currentCat = currentCat.Parent; } }
二、加载所有孙辈(向下遍历Children链)
假设你的Category实体定义了ICollection<Category> Children导航属性,要加载所有子级、孙级节点,可以这么做:
方法1:自定义递归Include扩展方法
EF Core没有内置递归Include的功能,但你可以自己写一个扩展方法来实现:
public static class QueryableExtensions { public static IQueryable<TEntity> IncludeRecursive<TEntity, TProperty>( this IQueryable<TEntity> query, Expression<Func<TEntity, TProperty>> navigationProperty) where TEntity : class where TProperty : IEnumerable<TEntity> { var memberExpr = (MemberExpression)navigationProperty.Body; var parameter = Expression.Parameter(typeof(TEntity), "e"); var propertyExpr = Expression.Property(parameter, memberExpr.Member.Name); var lambda = Expression.Lambda<Func<TEntity, IEnumerable<TEntity>>>(propertyExpr, parameter); // 递归调用Include和ThenInclude return query.Include(navigationProperty) .ThenInclude(lambda) .IncludeRecursive(lambda); } }
使用的时候直接调用这个扩展方法:
var link = FP.Links .Include(a => a.Category) .IncludeRecursive(c => c.Children) .FirstOrDefault(a => a.TransId == _transId && /* 你的其他条件 */);
注意哦:这个方法会一直递归到没有子节点为止,如果层级极深,可能会产生庞大的SQL查询,性能上要谨慎评估。
方法2:数据库CTE递归查询(高效推荐)
同样用CTE从目标节点向下递归查询所有孙辈,性能更可控:
var targetLink = FP.Links .Include(a => a.Category) .FirstOrDefault(a => a.TransId == _transId && /* 你的其他条件 */); if (targetLink?.Category == null) return; var allDescendants = FP.Categories .FromSqlRaw(@" WITH RECURSIVE CategoryHierarchy AS ( -- 起始节点:目标Category SELECT Id, ParentId, Name FROM Categories WHERE Id = {0} UNION ALL -- 递归关联子节点 SELECT c.Id, c.ParentId, c.Name FROM Categories c JOIN CategoryHierarchy ch ON c.ParentId = ch.Id ) SELECT * FROM CategoryHierarchy ", targetLink.Category.Id) .ToList();
总结一下
EF Core的Include/ThenInclude只能处理明确指定的固定层级,没法自动递归,这是正常特性,不是你操作失误。递归加载层级数据的核心思路就是:要么借助数据库的CTE能力一次性查询,要么在代码里手动处理递归逻辑,具体选哪种看你的层级深度和性能需求~
内容的提问来源于stack exchange,提问作者TheTalentedDrAl

