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

EF Core 8如何递归加载目录页面的所有子页面?

EF Core 8递归加载多级目录页面解决方案

我正在使用EF Core 8开发目录系统,系统中的页面可包含子页面及条目,需要递归加载所有层级的子页面。当前代码仅能加载一级子页面,无法递归到无后续子页面的层级。

当前代码

var page = await dbContext
                   .Set<CatalogPage>()
                   .Where(x => x.Id == eventParser.PageId)
                   .Include(c => c.Items)
                   .ThenInclude(x => x.FurnitureItems)
                   .Include(c => c.Pages)
                   .ThenInclude(x => x.Pages)
                   .FirstOrDefaultAsync();

实体类定义

public class CatalogPage
{
    [Key]
    public int Id { get; init; }
    public string? Name { get; init; }
    public string? Caption { get; init; }
    public string? Layout { get; init; }
    public int? RoleId { get; init; }
    public int CatalogPageId { get; init; }
    public int OrderId { get; init; }
    public int IconId { get; init; }
    public ICollection<CatalogPage> Pages { get; init; } = [];
    public ICollection<CatalogItem> Items { get; init; } = [];
}

public class CatalogItem
{
    public int Id { get; init; }
    public string? Name { get; init; }
    public int CostCredits { get; init; }
    public int CostPoints { get; init; }
    public ICollection<FurnitureItem> FurnitureItems { get; init; } = [];
    public string? MetaData { get; init; }
    public int Amount { get; init; }
    public int SellLimit { get; init; }
}

解决方案

方法1:启用延迟加载(快速实现)

EF Core的延迟加载可以自动递归加载子页面,但需满足两个条件:

  • 将导航属性标记为virtual
  • 确保DbContext启用延迟加载(默认已开启,若手动关闭需重新开启)

修改实体类导航属性:

public class CatalogPage
{
    // ...其他属性
    public virtual ICollection<CatalogPage> Pages { get; init; } = [];
    public virtual ICollection<CatalogItem> Items { get; init; } = [];
}

public class CatalogItem
{
    // ...其他属性
    public virtual ICollection<FurnitureItem> FurnitureItems { get; init; } = [];
}

修改后的查询代码(无需多层ThenInclude,访问子页面时会自动加载):

var page = await dbContext
                   .Set<CatalogPage>()
                   .Where(x => x.Id == eventParser.PageId)
                   .Include(c => c.Items)
                   .ThenInclude(x => x.FurnitureItems)
                   .FirstOrDefaultAsync();

注意:延迟加载可能导致N+1查询问题,数据量较大时不推荐使用。

方法2:递归CTE+内存构建树(性能最优)

通过SQL递归CTE一次性获取根节点及所有后代页面,再在内存中构建层级结构,避免N+1查询。

// 1. 执行递归CTE查询所有相关页面
var allPages = await dbContext.Set<CatalogPage>()
    .Include(c => c.Items)
        .ThenInclude(x => x.FurnitureItems)
    .FromSqlRaw(@"
        WITH RecursivePages AS (
            SELECT * FROM CatalogPages WHERE Id = {0}
            UNION ALL
            SELECT cp.* FROM CatalogPages cp
            JOIN RecursivePages rp ON cp.CatalogPageId = rp.Id
        )
        SELECT * FROM RecursivePages", eventParser.PageId)
    .ToListAsync();

// 2. 在内存中构建页面树
var rootPage = allPages.FirstOrDefault(x => x.Id == eventParser.PageId);
if (rootPage != null)
{
    BuildPageTree(rootPage, allPages);
}

// 递归构建子页面的辅助方法
void BuildPageTree(CatalogPage parent, List<CatalogPage> allPages)
{
    parent.Pages = allPages.Where(x => x.CatalogPageId == parent.Id).ToList();
    foreach (var childPage in parent.Pages)
    {
        BuildPageTree(childPage, allPages);
    }
}

优势:仅执行一次数据库查询,性能高效,适合数据量大的场景。

方法3:显式递归加载(手动控制)

先加载根节点,再递归查询并填充子页面,适合需要精细控制加载逻辑的场景。

var rootPage = await dbContext.Set<CatalogPage>()
    .Include(c => c.Items)
        .ThenInclude(x => x.FurnitureItems)
    .FirstOrDefaultAsync(x => x.Id == eventParser.PageId);

if (rootPage != null)
{
    await LoadChildPagesAsync(rootPage);
}

// 递归加载子页面的辅助方法
async Task LoadChildPagesAsync(CatalogPage parent)
{
    await dbContext.Entry(parent)
        .Collection(p => p.Pages)
        .Query()
        .Include(p => p.Items)
            .ThenInclude(x => x.FurnitureItems)
        .LoadAsync();

    foreach (var childPage in parent.Pages)
    {
        await LoadChildPagesAsync(childPage);
    }
}

注意:每一层子页面都会触发一次数据库查询,数据层级较深时会产生多次查询。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 04:58:21