如何使用Entity Framework Core构建层级分类与关联Recipe的复杂对象?
解决递归分类层级构建及Recipe关联问题
看起来你在构建递归分类结构时遇到了不少问题——比如层级构建失败、叶子节点的Recipe关联错误,还有可能存在的N+1数据库查询性能问题。我来帮你梳理下问题,并给出可行的修复方案。
先分析原代码的核心问题
- N+1查询问题:递归过程中每次调用
GetAllCategories都会查询数据库,随着分类层级加深,查询次数会指数级增长,性能极差。 - 根分类处理不全:当
parentId=0时只取第一个Level=0的分类,如果存在多个根分类会直接丢失。 - 实体映射错误:把
CategoryDto映射回Category添加到子分类列表,类型转换逻辑混乱,且没有正确填充当前分类的基础属性(比如id、categoryName等)。 - 叶子节点关联错误:直接将中间表
CategoryRecipe映射到RecipeTemplate,但实际需要关联的是Recipe实体,而非中间表对象。
最优解决方案:一次性拉取数据+内存构建层级
推荐先一次性拉取所有需要的数据,再在内存中构建层级结构,这样既避免了多次数据库查询,也能更清晰地处理分类与Recipe的关联。
完整代码示例
// 假设你有Recipe的仓储接口:IRecipeRepository _recipeRepository; public List<Category> BuildFullCategoryHierarchy() { // 一次性拉取所有核心数据,避免N+1查询 var allCategories = _categoryRepository.GetAll().ToList(); var allCategoryRecipes = _categoryRecipeTypesRepository.GetAll().ToList(); var allRecipes = _recipeRepository.GetAll().ToList(); // 建立ID到Category的映射字典,方便快速查找父/子分类 var categoryMap = allCategories.ToDictionary(c => c.id, c => new Category { id = c.id, categoryName = c.categoryName, categoryDesc = c.categoryDesc, isLast = c.isLast, parentId = c.parentId, level = c.level, Category = new List<Category>(), Recipe = new List<Recipe>() }); // 处理叶子节点的Recipe关联 foreach (var leafCategory in categoryMap.Values.Where(c => c.isLast)) { // 获取当前分类关联的所有RecipeID var linkedRecipeIds = allCategoryRecipes .Where(cr => cr.CategoryId == leafCategory.id) .Select(cr => cr.RecipeId) .ToList(); // 匹配对应的Recipe实体 leafCategory.Recipe = allRecipes .Where(r => linkedRecipeIds.Contains(r.id)) .ToList(); } // 构建分类层级:将子分类添加到父分类的Category列表中 foreach (var category in categoryMap.Values) { if (category.parentId != 0 && categoryMap.ContainsKey(category.parentId)) { categoryMap[category.parentId].Category.Add(category); } } // 返回所有根分类(根据业务规则,这里取parentId=0的分类) return categoryMap.Values.Where(c => c.parentId == 0).ToList(); }
方案优势
- 性能优化:仅3次数据库查询,不管分类层级多少,性能都稳定。
- 逻辑清晰:先处理叶子节点的Recipe关联,再构建层级,避免递归中的混乱。
- 完整性保障:所有根分类都会被返回,不会丢失数据。
如果你坚持使用递归方式(不推荐用于大数据量)
如果业务场景分类层级少、数据量小,也可以修复原递归方法,以下是修正后的版本:
// 修正递归方法:返回List<Category>,因为一个父分类可能有多个子分类 public List<Category> GetCategoryHierarchy(int parentId) { // 获取当前父分类下的所有子分类 var childCategories = _categoryRepository.GetAll().Where(c => c.parentId == parentId).ToList(); if (!childCategories.Any()) return new List<Category>(); foreach (var category in childCategories) { if (category.isLast) { // 关联Recipe实体 var recipeIds = _categoryRecipeTypesRepository.GetAll() .Where(cr => cr.CategoryId == category.id) .Select(cr => cr.RecipeId) .ToList(); category.Recipe = _recipeRepository.GetAll() .Where(r => recipeIds.Contains(r.id)) .ToList(); } else { // 递归获取子分类层级 category.Category = GetCategoryHierarchy(category.id); } } return childCategories; } // 调用方式:获取所有根分类 var rootCategories = GetCategoryHierarchy(0);
递归方法修正点
- 调整返回类型为
List<Category>,符合一个父分类对应多个子分类的业务逻辑。 - 叶子节点正确关联
Recipe实体,而非中间表对象。 - 移除了原方法中混乱的
CategoryDto转换逻辑,直接操作Category实体。
额外提醒
如果使用AutoMapper,务必确保Category与CategoryDto、Recipe与对应Dto的映射配置正确,避免属性丢失或类型转换错误。
内容的提问来源于stack exchange,提问作者Vivek Nuna
相关产品推荐
相关产品推荐

