如何使用LINQ实现双层分组:先按项目再按日期聚合数据
解决LINQ双层分组:先按项目,再按日期汇总的问题
你当前的代码逻辑是直接按项目+日期作为分组键,导致每个项目的每一天都生成了独立的GroupedBillingInfo对象,而不是把同一项目的所有日期数据归集到同一个对象的Data数组里。咱们调整一下分组顺序,先按项目分组,再在项目内部按日期汇总就能解决问题啦!
修改后的代码实现
public async Task<List<GroupedBillingInfo>> Handle(GroupedBillingListQuery request, CancellationToken cancellationToken) { var billing = _context.BillingSummaries .Where(x => x.Project.Organization.IsDeleted == false) // 提前关联Project实体,避免EF的N+1查询问题 .Include(x => x.Project) .AsQueryable(); var demo = await billing.ToListAsync(cancellationToken); var result = demo // 第一层分组:按项目ID聚合所有相关账单 .GroupBy(x => x.ProjectId) .Select(projectGroup => new GroupedBillingInfo { ProjectId = projectGroup.Key, // 同一项目的名称一致,取任意一条即可 ProjectName = projectGroup.First().Project.Name, // 第二层分组:在当前项目内按日期汇总价格 Data = projectGroup // 按日期的年月日分组,忽略时间部分 .GroupBy(billingItem => new { billingItem.BeginApply.Year, billingItem.BeginApply.Month, billingItem.BeginApply.Day }) .Select(dateGroup => new GroupedBillings { // 格式化为你期望的ISO日期字符串 StartDate = new DateTime(dateGroup.Key.Year, dateGroup.Key.Month, dateGroup.Key.Day) .ToString("yyyy-MM-ddTHH:mm:ss"), // 计算当天的价格总和 Price = dateGroup.Sum(item => item.Price) }) .ToList() }) .ToList(); return result; } // 你的实体类无需修改 public class GroupedBillings { public string StartDate { get; set; } public decimal Price { get; set; } } public class GroupedBillingInfo { public List<GroupedBillings> Data { get; set; } public int ProjectId { get; set; } public string ProjectName { get; set; } }
关键逻辑说明
- 第一层分组:仅按
ProjectId分组,确保同一个项目的所有账单记录都被聚合到同一个分组中。 - 第二层分组:在每个项目分组内部,按账单日期的「年/月/日」进行二次分组,计算该项目当天的总费用。
- 日期格式化:通过
DateTime构造函数明确生成日期对象,再转成标准的ISO格式字符串,避免手动拼接字符串可能出现的格式不一致问题。 - 性能优化:添加
.Include(x => x.Project)提前加载关联的Project实体,避免Entity Framework在后续取Project.Name时触发多次数据库查询(N+1问题)。
这样处理后,返回的结果结构就完全符合你的预期:每个项目对应一个对象,其Data数组包含该项目所有日期的费用汇总。
内容的提问来源于stack exchange,提问作者Arzu Suleymanov
相关产品推荐
相关产品推荐

