如何改写LINQ查询以实现含各分类产品的递归商品分类树?
构建带产品的递归商品分类树解决方案
嘿,我来帮你搞定这个需求!要把现有的分类查询改成包含产品的递归分类树,咱们一步步来:
1. 先明确实体与视图模型结构
首先得确保你的实体和视图模型包含必要的关联属性,我先假设你的模型大概是这样(如果和你的实际结构有出入,调整对应字段就行):
实体模型
public class Category { public int Id { get; set; } public string Name { get; set; } public int? ParentId { get; set; } // 关联父分类 public virtual Category Parent { get; set; } // 关联子分类 public virtual ICollection<Category> Children { get; set; } = new List<Category>(); // 分类下的产品 public virtual ICollection<Product> Products { get; set; } = new List<Product>(); } public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; } }
视图模型
public class CategoryViewModel { public int Id { get; set; } public string Name { get; set; } // 子分类视图模型列表 public List<CategoryViewModel> Children { get; set; } = new List<CategoryViewModel>(); // 当前分类下的产品列表 public List<ProductViewModel> Products { get; set; } = new List<ProductViewModel>(); } public class ProductViewModel { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } }
2. 改写LINQ查询:加载分类+子分类+产品
如果用Entity Framework Core,你可以用Include + ThenInclude来一次性加载所有关联数据,再递归整理成树形结构;或者用递归查询(CTE)来获取层级数据。这里推荐两种方式:
方式一:EF Core 预加载所有数据,内存中构建树形(适合数据量不大的场景)
// 获取所有分类,同时加载子分类和产品 var allCategories = await _dbContext.Categories .Include(c => c.Children) .Include(c => c.Products) .ToListAsync(); // 筛选出顶级分类(ParentId为null的),并构建树形结构 var rootCategories = allCategories .Where(c => c.ParentId == null) .ToList();
方式二:用SQL CTE递归查询(适合大数据量,减少内存压力)
如果数据量比较大,可以直接写SQL CTE查询,然后映射到模型:
var recursiveCategoriesQuery = _dbContext.Categories .FromSqlRaw(@" WITH RecursiveCategories AS ( SELECT Id, Name, ParentId, 0 as Level FROM Categories WHERE ParentId IS NULL UNION ALL SELECT c.Id, c.Name, c.ParentId, rc.Level + 1 FROM Categories c JOIN RecursiveCategories rc ON c.ParentId = rc.Id ) SELECT rc.*, p.* FROM RecursiveCategories rc LEFT JOIN Products p ON rc.Id = p.CategoryId ") .AsEnumerable() // 先加载到内存,再整理树形 .GroupBy(c => c.Id) .Select(g => new Category { Id = g.Key, Name = g.First().Name, ParentId = g.First().ParentId, Products = g.Where(p => p.Id != null).Select(p => new Product { Id = p.Id, Name = p.Name, Price = p.Price }).ToList() }) .ToList(); // 再构建树形结构(筛选ParentId为null的顶级分类) var rootCategories = recursiveCategoriesQuery.Where(c => c.ParentId == null).ToList();
3. AutoMapper 配置递归映射
要让AutoMapper自动处理递归的分类和产品映射,你需要在Profile里配置:
public class MappingProfile : Profile { public MappingProfile() { // 分类映射:包含子分类和产品 CreateMap<Category, CategoryViewModel>() .ForMember(dest => dest.Children, opt => opt.MapFrom(src => src.Children)) .ForMember(dest => dest.Products, opt => opt.MapFrom(src => src.Products)); // 产品映射 CreateMap<Product, ProductViewModel>(); } }
这样当你映射顶级分类时,AutoMapper会自动递归映射所有子分类和对应的产品。
4. 控制器逻辑
在控制器里获取数据,用AutoMapper转换,传给视图:
private readonly IMapper _mapper; private readonly AppDbContext _dbContext; public CategoryController(AppDbContext dbContext, IMapper mapper) { _dbContext = dbContext; _mapper = mapper; } public async Task<IActionResult> Index() { // 获取所有分类(带子分类和产品) var allCategories = await _dbContext.Categories .Include(c => c.Children) .Include(c => c.Products) .ToListAsync(); // 筛选顶级分类 var rootCategories = allCategories.Where(c => c.ParentId == null).ToList(); // 用AutoMapper转换为视图模型 var categoryViewModels = _mapper.Map<List<CategoryViewModel>>(rootCategories); return View(categoryViewModels); }
5. 视图渲染:递归局部视图
Index.cshtml(主视图)
@model List<CategoryViewModel> <h1>商品分类树</h1> <div class="category-tree"> @foreach (var category in Model) { @await Html.PartialAsync("_CategoryAndProductRecursive", category) } </div>
_CategoryAndProductRecursive.cshtml(递归局部视图)
@model CategoryViewModel <div class="category-node"> <h3>@Model.Name</h3> <!-- 渲染当前分类下的产品 --> @if (Model.Products.Any()) { <ul class="product-list"> @foreach (var product in Model.Products) { <li>@product.Name - @product.Price.ToString("C")</li> } </ul> } <!-- 递归渲染子分类 --> @if (Model.Children.Any()) { <div class="child-categories ml-4"> @foreach (var childCategory in Model.Children) { @await Html.PartialAsync("_CategoryAndProductRecursive", childCategory) } </div> } </div>
这样就能完美构建出包含所有层级分类和对应产品的递归分类树啦!如果你的实体模型有特殊字段,只需要调整对应的映射和视图部分就行。
内容的提问来源于stack exchange,提问作者Stian
相关产品推荐
相关产品推荐

