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

C# .NET Entity Framework 递归Include未知层级关联实体方案

EF Core 无限层级自关联实体加载方案

EF Core 原生的Include/ThenInclude链式写法不支持未知深度的递归预加载,针对自关联Thing1实体的全层级加载场景,有两种可直接落地的实现方式:

方案1:动态拼接Include加载路径

Include方法支持传入字符串格式的导航属性路径,不需要硬编码Lambda链式调用,你可以提前计算当前实体下的最大嵌套深度,再逐段拼接加载路径,同时覆盖每一层级下关联的Thing2实体。

步骤1:计算当前MainThing下Thing1的最大嵌套深度

// 先取出当前MainThing关联的所有Thing1的Id和自关联外键
var thing1Refs = await _db.Thing1
    .Where(t => t.MainThingId == memainthingId)
    .Select(t => new { t.Id, t.ParentId })
    .ToListAsync();

int maxDepth = 0;
// 递归计算最大嵌套深度
void CalcDepth(int? parentId, int currentDepth)
{
    if (currentDepth > maxDepth) maxDepth = currentDepth;
    var childIds = thing1Refs.Where(t => t.ParentId == parentId).Select(t => t.Id);
    foreach (var cid in childIds)
    {
        CalcDepth(cid, currentDepth + 1);
    }
}
// 顶级Thing1的ParentId为空,从0层开始计算
CalcDepth(null, 0);

步骤2:动态拼接Include路径执行查询

var query = _db.Mainthing.AsQueryable();
// 加载第一级Thing1及关联的Thing2
query = query.Include("thing1.thing2");
string currentNavPath = "thing1";
// 从第二级开始逐段追加自关联路径,每一级都加载对应的Thing2
for (int i = 1; i <= maxDepth; i++)
{
    currentNavPath += ".thing1";
    query = query.Include($"{currentNavPath}.thing2");
}

var copy = await query
    .AsSplitQuery()
    .AsNoTrackingWithIdentityResolution()
    .FirstOrDefaultAsync(x => x.Id == memainthingId);

如果你的业务有明确的嵌套上限(比如最多允许嵌套10层),可以跳过计算最大深度的步骤,直接按上限拼接路径即可。由于你开启了身份解析,多拼接的超出实际深度的路径不会引发报错,仅会生成少量冗余查询,对性能影响极小。


方案2:分层递归显式加载

如果你不想动态拼接查询路径,可以分层递归查询每一级的子实体,依托EF Core的身份解析能力,查出来的实体会自动挂载到对应父实体的导航属性上,不需要手动做映射赋值。

// 先加载主实体和第一级Thing1、关联Thing2
var copy = await _db.Mainthing
    .Include(x => x.thing1).ThenInclude(x => x.thing2)
    .AsSplitQuery()
    .AsNoTrackingWithIdentityResolution()
    .FirstOrDefaultAsync(x => x.Id == memainthingId);

if (copy == null)
{
    // 处理主实体不存在的逻辑
    return;
}

var currentLevel = copy.thing1.ToList();
// 循环加载下一级子项,直到没有更深层级为止
while (currentLevel.Any())
{
    var parentIds = currentLevel.Select(t => t.Id).ToList();
    // 查询下一级的所有Thing1和关联的Thing2
    var nextLevel = await _db.Thing1
        .Include(t => t.thing2)
        .Where(t => t.ParentId.HasValue && parentIds.Contains(t.ParentId.Value))
        .AsSplitQuery()
        .ToListAsync();
    
    currentLevel = nextLevel;
}

这个方案不需要提前计算深度,每一次查询只拉取对应层级的必要数据,没有冗余查询,适合嵌套深度差异大、数据量较高的场景。注意必须开启身份解析(默认跟踪查询或AsNoTrackingWithIdentityResolution),才能实现导航属性的自动挂载。

注意:EF Core目前不支持在Include中自动生成递归CTE实现单SQL递归加载,硬写原生SQL递归的话后续实体映射、维护成本很高,上述两种方案已经能覆盖绝大多数业务场景,性能表现稳定。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 17:30:49