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

如何使用Entity Framework Core构建层级分类与关联Recipe的复杂对象?

解决递归分类层级构建及Recipe关联问题

看起来你在构建递归分类结构时遇到了不少问题——比如层级构建失败、叶子节点的Recipe关联错误,还有可能存在的N+1数据库查询性能问题。我来帮你梳理下问题,并给出可行的修复方案。

先分析原代码的核心问题

  1. N+1查询问题:递归过程中每次调用GetAllCategories都会查询数据库,随着分类层级加深,查询次数会指数级增长,性能极差。
  2. 根分类处理不全:当parentId=0时只取第一个Level=0的分类,如果存在多个根分类会直接丢失。
  3. 实体映射错误:把CategoryDto映射回Category添加到子分类列表,类型转换逻辑混乱,且没有正确填充当前分类的基础属性(比如id、categoryName等)。
  4. 叶子节点关联错误:直接将中间表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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:42:56