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

