.NET中如何返回指定分类下所有子分类的商品
嵌套分类商品查询功能实现
需求说明
点击父分类(如「服饰」)时,返回该分类及其所有多级子分类下的全部商品。
现有分类模型
[Key] public int Id { get; set; } public string Name { get; set; } [JsonIgnore] [ForeignKey("parentID")] public Kategorija parentCategory { get; set; } public List<Kategorija> Subcategories { get; set; } [JsonIgnore] public List<Proizvod> Proizvodi { get; set; }
现有分类数据接口
[Route("PreuzmiKategorije")] [HttpGet] public async Task<ActionResult> preuzmiKategorije() { try { var kategorije = await Context.Kategorijas.Include(c => c.Subcategories).ToListAsync(); List<Kategorija> result = new List<Kategorija>(); List<Kategorija> Rez = new List<Kategorija>(); HashSet<int> processedCategoryIds = new HashSet<int>(); foreach (var category in kategorije) { if (!processedCategoryIds.Contains(category.Id)) { result.Add(category); processedCategoryIds.Add(category.Id); AddSubcategoriesRecursive(category.Subcategories, result, processedCategoryIds); } } foreach(var res in result) { if(res.parentCategory == null) { Rez.Add(res); } } return Ok(Rez); } catch(Exception e) { return BadRequest(e.Message); } } private void AddSubcategoriesRecursive(IEnumerable<Kategorija> subcategories, List<Kategorija> result, HashSet<int> processedCategoryIds) { foreach (var subcategory in subcategories) { if (!processedCategoryIds.Contains(subcategory.Id)) { result.Add(subcategory); processedCategoryIds.Add(subcategory.Id); AddSubcategoriesRecursive(subcategory.Subcategories, result, processedCategoryIds); } } }
问题代码(商品查询接口)
[Route("PreuzmiProizvodePodkategorija")] [HttpGet] public async Task<ActionResult> PreuzmiProizvodePodkategorija(string kategorija) { try { var kategorijaZaPretragu = await context.Kategorijas .Include(c => c.Subcategories) .Include(c => c.Proizvodi) .Where(c => c.Name == kategorija).FirstOrDefaultAsync(); if (kategorijaZaPretragu == null) { return NotFound(); } var proizvodi = new List<Proizvod>(); DodajProizvodePodkategorija(kategorijaZaPretragu.Subcategories, proizvodi); return Ok(proizvodi); } catch (Exception e) { return BadRequest(e.Message); } } private void DodajProizvodePodkategorija(IEnumerable<Kategorija> subkategorije, List<Proizvod> proizvodi) { foreach (var subkategorija in subkategorije) { proizvodi.AddRange(subkategorija.Proizvodi); DodajProizvodePodkategorija(subkategorija.Subcategories, proizvodi); } }
修正方案
问题分析
- 未加载深层子分类的商品:原查询仅Include了第一层子分类和商品,深层子分类的
Proizvodi未被加载,导致递归时无法获取深层商品。 - 未包含父分类自身的商品:原逻辑只遍历子分类的商品,若父分类本身有商品会被遗漏。
- 递归加载效率较低:通过EF递归加载嵌套分类会产生多次查询,性能较差。
方案一:优化EF查询并修复递归逻辑
如果需要保留递归加载的方式,需确保所有层级的子分类和商品都被正确加载,同时加入父分类自身的商品:
[Route("PreuzmiProizvodePodkategorija")] [HttpGet] public async Task<ActionResult> PreuzmiProizvodePodkategorija(string kategorija) { try { // 使用递归Include加载所有层级的子分类及其商品 var kategorijaZaPretragu = await context.Kategorijas .Include(c => c.Proizvodi) .Include(c => c.Subcategories) .ThenInclude(sc => sc.Proizvodi) .Include(c => c.Subcategories) .ThenInclude(sc => sc.Subcategories) .ThenInclude(ssc => ssc.Proizvodi) // 如果分类层级更深,需继续添加ThenInclude,或启用EF Core延迟加载 .Where(c => c.Name == kategorija).FirstOrDefaultAsync(); if (kategorijaZaPretragu == null) { return NotFound(); } var proizvodi = new List<Proizvod>(); // 先添加父分类自身的商品 proizvodi.AddRange(kategorijaZaPretragu.Proizvodi); // 递归添加所有子分类的商品 DodajProizvodePodkategorija(kategorijaZaPretragu.Subcategories, proizvodi); return Ok(proizvodi); } catch (Exception e) { return BadRequest(e.Message); } } private void DodajProizvodePodkategorija(IEnumerable<Kategorija> subkategorije, List<Proizvod> proizvodi) { foreach (var subkategorija in subkategorije) { proizvodi.AddRange(subkategorija.Proizvodi); // 递归处理子分类的子分类 DodajProizvodePodkategorija(subkategorija.Subcategories, proizvodi); } }
方案二:更高效的批量查询方式(推荐)
先通过递归获取该分类下的所有子分类ID(包括自身),然后直接查询这些分类下的所有商品,避免多次EF查询:
[Route("PreuzmiProizvodePodkategorija")] [HttpGet] public async Task<ActionResult> PreuzmiProizvodePodkategorija(string kategorija) { try { var targetCategory = await context.Kategorijas .FirstOrDefaultAsync(c => c.Name == kategorija); if (targetCategory == null) { return NotFound(); } // 获取所有相关分类ID(自身+所有多级子分类) var categoryIds = new HashSet<int>(); GetAllCategoryIds(targetCategory.Id, categoryIds); // 批量查询这些分类下的所有商品 var proizvodi = await context.Proizvodis .Where(p => categoryIds.Contains(p.KategorijaId)) // 假设Proizvod有KategorijaId外键 .ToListAsync(); return Ok(proizvodi); } catch (Exception e) { return BadRequest(e.Message); } } // 递归获取所有子分类ID private void GetAllCategoryIds(int categoryId, HashSet<int> categoryIds) { if (categoryIds.Contains(categoryId)) return; categoryIds.Add(categoryId); // 查询当前分类的子分类 var subcategories = context.Kategorijas .Where(c => c.parentID == categoryId) .Select(c => c.Id) .ToList(); foreach (var subId in subcategories) { GetAllCategoryIds(subId, categoryIds); } }
补充说明
- 方案二性能更优,尤其当分类层级较深或商品数量较多时,避免了EF嵌套加载的开销。
- 若使用方案一且分类层级不确定,建议开启EF Core延迟加载(需在DbContext配置中启用,且导航属性设为virtual),深层子分类的商品会在访问时自动加载。
- 分类名称可能重复,建议改为通过分类ID查询,避免同名分类导致的错误。
内容的提问来源于stack exchange,提问作者Nikola
相关产品推荐
相关产品推荐

