如何用LinQ筛选含IsInActivity=true子项的父子Category列表?
解决树形分类的递归筛选问题
这个树形分类的筛选需求我之前也碰到过,核心就是要构建一个保留所有包含有效资源(IsInActivity=true)或后代包含有效资源的分类的树形结构,同时每个分类下只保留符合条件的子分类,并且只展示有效资源。我分两种场景给你解决方案:一种是基于EF(数据库层面先筛选),另一种是内存中递归处理。
方案一:EF中先筛选所有有效分类ID,再构建树
这种方式更高效,因为先在数据库层面找出所有需要保留的分类,再构建树形结构。
步骤1:获取所有需要保留的分类ID
我们需要两类分类ID:
- 直接关联
IsInActivity=true的ExternalResource的分类; - 上述分类的所有祖先分类(因为子分类有有效资源时,父分类要展示)。
用递归的方式获取这些ID:
// 1. 先获取所有直接关联有效资源的分类ID var validCategoryIds = db.ExternalResources .Where(er => er.IsInActivity) .Select(er => er.CategoryID) .Distinct() .ToList(); // 2. 递归获取这些分类的所有祖先ID var allValidIds = new HashSet<long>(validCategoryIds); var parentIds = validCategoryIds; do { parentIds = db.Categories .Where(c => parentIds.Contains(c.CategoryID)) .Select(c => c.ParentCategoryID) .Where(id => id.HasValue) .Select(id => id.Value) .Distinct() .Except(allValidIds) .ToList(); foreach (var id in parentIds) { allValidIds.Add(id); } } while (parentIds.Any());
步骤2:构建符合要求的树形结构
接下来我们从数据库获取这些有效分类,然后递归构建树,同时过滤子分类和资源:
// 获取所有有效分类并预加载导航属性 var validCategories = db.Categories .Where(c => allValidIds.Contains(c.CategoryID)) .Include(c => c.Subcategories) .Include(c => c.ExternalResources) .ToList() .Select(c => MapToValidCategory(c, allValidIds)) .ToList(); // 最终结果取根分类(ParentCategoryID为null的) var finalResult = validCategories.Where(c => !c.ParentCategoryID.HasValue).ToList(); // 辅助方法:映射分类并过滤子分类和资源 private Category MapToValidCategory(Category original, HashSet<long> validIds) { return new Category { CategoryID = original.CategoryID, Name = original.Name, ParentCategoryID = original.ParentCategoryID, // 只保留属于有效ID的子分类 Subcategories = original.Subcategories .Where(sc => validIds.Contains(sc.CategoryID)) .Select(sc => MapToValidCategory(sc, validIds)) .ToList(), // 只保留IsInActivity=true的资源 ExternalResources = original.ExternalResources.Where(er => er.IsInActivity).ToList() }; }
方案二:内存中递归筛选整个分类树
如果你的分类数据量不大,可以先加载所有分类到内存,再递归筛选符合条件的节点:
// 加载所有分类并预加载导航属性 var allCategories = db.Categories .Include(c => c.Subcategories) .Include(c => c.ExternalResources) .ToList(); // 递归判断:分类自身或后代是否有有效资源 Func<Category, bool> IsCategoryValid = null; IsCategoryValid = (cat) => cat.ExternalResources.Any(er => er.IsInActivity) || cat.Subcategories.Any(sc => IsCategoryValid(sc)); // 构建符合条件的树形结构 var finalResult = allCategories .Where(c => !c.ParentCategoryID.HasValue) .Select(c => BuildValidCategoryTree(c, IsCategoryValid)) .Where(c => c != null) .ToList(); // 辅助方法:递归构建有效分类树 private Category BuildValidCategoryTree(Category original, Func<Category, bool> isValid) { // 如果当前分类不符合条件,直接返回null(不保留) if (!isValid(original)) return null; return new Category { CategoryID = original.CategoryID, Name = original.Name, ParentCategoryID = original.ParentCategoryID, // 递归处理子分类,只保留符合条件的 Subcategories = original.Subcategories .Select(sc => BuildValidCategoryTree(sc, isValid)) .Where(sc => sc != null) .ToList(), // 过滤有效资源 ExternalResources = original.ExternalResources.Where(er => er.IsInActivity).ToList() }; }
关键说明
两种方案都能满足你的需求:
- 所有包含有效资源的分类,以及它们的所有父分类都会被展示;
- 每个分类下的子分类只保留自身或后代有有效资源的节点;
- 每个分类的
ExternalResources仅保留IsInActivity=true的项。
如果用EF Core,还可以用递归CTE来更高效地获取所有有效分类ID,替代方案一中的递归循环:
// 递归CTE获取所有有效分类ID(自身有有效资源或后代有) var cte = db.Categories .Where(c => db.ExternalResources.Any(er => er.CategoryID == c.CategoryID && er.IsInActivity)) .Select(c => new { c.CategoryID, c.ParentCategoryID }) .Union( db.Categories .Join(cte, c => c.CategoryID, cte => cte.ParentCategoryID, (c, cte) => new { c.CategoryID, c.ParentCategoryID }) ); var allValidIds = cte.Select(x => x.CategoryID).Distinct().ToList();
内容的提问来源于stack exchange,提问作者Jason0495
相关产品推荐
相关产品推荐

