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

ASP.NET Core EF如何查询指定分类及其所有子分类的文档?

解决方案:获取指定分类及其所有子分类下的文档

要实现选择分类时返回该分类及所有子分类的文档,核心是先拿到该分类树的所有ID,再用EF的Contains方法实现SQL的IN逻辑。下面提供几种适用于不同场景的实现方式:

方法1:递归查询分类ID(兼容所有EF版本)

通过递归遍历分类结构,收集目标分类及其所有子分类的ID,再用这些ID筛选文档。

步骤1:添加递归获取分类ID的方法

// 获取指定分类及其所有子分类的ID集合
private async Task<List<int>> GetCategoryAndDescendantIdsAsync(int categoryId)
{
    var categoryIds = new List<int>();
    await RecursivelyAddIds(categoryId, categoryIds);
    return categoryIds;
}

private async Task RecursivelyAddIds(int currentId, List<int> categoryIds)
{
    // 添加当前分类ID
    categoryIds.Add(currentId);
    
    // 查询当前分类的直接子分类
    var childCategories = await _documentDbContext.DocumentCategories
        .Where(c => c.ParentId == currentId)
        .ToListAsync();
    
    // 递归处理每个子分类
    foreach (var child in childCategories)
    {
        await RecursivelyAddIds(child.Id, categoryIds);
    }
}

步骤2:修改控制器Action

public async Task<ActionResult> DocsByCatAsync(int id)
{
    var documents = new List<Document>();
    if (id == 0)
    {
        documents = await _documentDbContext
            .Documents
            .AsNoTracking()
            .Include(d => d.Category)
            .ToListAsync();
    }
    else
    {
        // 获取所有相关分类ID
        var categoryIds = await GetCategoryAndDescendantIdsAsync(id);
        
        // 筛选属于这些分类的文档(EF会自动转为SQL的IN语句)
        documents = await _documentDbContext
            .Documents
            .Where(d => categoryIds.Contains(d.Category.Id))
            .AsNoTracking()
            .Include(d => d.Category)
            .ToListAsync();
    }

    return PartialView("_DocumentTable", documents);
}

方法2:使用EF Core递归CTE(EF Core 5+推荐)

如果使用EF Core 5及以上版本,直接用数据库的递归CTE(公共表表达式)在数据库端完成分类树查询,性能更优,尤其适合分类数量多、层级深的场景。

修改控制器Action

public async Task<ActionResult> DocsByCatAsync(int id)
{
    var documents = new List<Document>();
    if (id == 0)
    {
        documents = await _documentDbContext
            .Documents
            .AsNoTracking()
            .Include(d => d.Category)
            .ToListAsync();
    }
    else
    {
        // 用CTE递归查询所有相关分类ID
        var categoryIds = await _documentDbContext.DocumentCategories
            .FromSqlRaw(@"
                WITH RecursiveCategories AS (
                    SELECT Id FROM DocumentCategories WHERE Id = {0}
                    UNION ALL
                    SELECT c.Id FROM DocumentCategories c
                    JOIN RecursiveCategories rc ON c.ParentId = rc.Id
                )
                SELECT Id FROM RecursiveCategories", id)
            .Select(c => c.Id)
            .ToListAsync();
        
        // 筛选文档
        documents = await _documentDbContext
            .Documents
            .Where(d => categoryIds.Contains(d.Category.Id))
            .AsNoTracking()
            .Include(d => d.Category)
            .ToListAsync();
    }

    return PartialView("_DocumentTable", documents);
}

方法3:内存中递归筛选(适合分类数量少的场景)

如果系统中分类数量不多,可以一次性加载所有分类到内存,再在内存中递归筛选ID,减少数据库查询次数。

步骤1:添加内存递归筛选方法

// 从所有分类中递归获取目标分类及其子分类的ID
private List<int> GetCategoryAndDescendantIds(int categoryId, List<DocumentCategory> allCategories)
{
    var ids = new List<int>();
    var currentCategory = allCategories.First(c => c.Id == categoryId);
    ids.Add(currentCategory.Id);
    
    // 找到所有直接子分类
    var childCategories = allCategories.Where(c => c.ParentId == currentCategory.Id).ToList();
    foreach (var child in childCategories)
    {
        ids.AddRange(GetCategoryAndDescendantIds(child.Id, allCategories));
    }
    return ids;
}

步骤2:修改控制器Action

public async Task<ActionResult> DocsByCatAsync(int id)
{
    var documents = new List<Document>();
    if (id == 0)
    {
        documents = await _documentDbContext
            .Documents
            .AsNoTracking()
            .Include(d => d.Category)
            .ToListAsync();
    }
    else
    {
        // 一次性加载所有分类到内存
        var allCategories = await _documentDbContext.DocumentCategories.ToListAsync();
        var categoryIds = GetCategoryAndDescendantIds(id, allCategories);
        
        // 筛选文档
        documents = await _documentDbContext
            .Documents
            .Where(d => categoryIds.Contains(d.Category.Id))
            .AsNoTracking()
            .Include(d => d.Category)
            .ToListAsync();
    }

    return PartialView("_DocumentTable", documents);
}

关键说明

EF中categoryIds.Contains(d.Category.Id)会被自动转换为SQL的WHERE ... IN (...)语句,完全符合你需要的逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 10:01:18