C#结合LINQ实现月度内每周工时汇总 优化重复查询逻辑
解决方案
现存问题
你当前的写法存在三个明显问题:
- 对同一数据源重复执行4次
Sum遍历,存在不必要的性能损耗 - 逐周硬编码判断逻辑,后续周数调整(比如当月跨5周)需要手动新增重复代码块,维护成本高
- 前后端耦合度高,周范围计算依赖前端moment.js的结果,容易因为两端周起始规则不一致出现统计偏差
1. 日期周范围计算实现
直接给日期类型写扩展方法,不需要依赖前端传入周起止参数,后端可直接根据ShiftDate计算所属周的范围,默认周起始日为周一,可按需修改为周日:
public static class DateTimeExtension { /// <summary> /// 获取指定日期所在周的起止时间 /// </summary> /// <param name="date">输入日期</param> /// <param name="weekStartDay">周起始日,默认周一</param> /// <returns>(周开始日期0点,周结束日期23:59:59)</returns> public static (DateTime Start, DateTime End) GetWeekRange(this DateTime date, DayOfWeek weekStartDay = DayOfWeek.Monday) { int offset = (7 + (date.DayOfWeek - weekStartDay)) % 7; DateTime start = date.AddDays(-offset).Date; DateTime end = start.AddDays(6).Date.AddSeconds(86399); // 取当天最后一秒 return (start, end); } }
2. 单次遍历完成全周工时统计
核心思路是把所有待统计的周规则放到集合中统一管理,只遍历一次班次数据源,就能把所有工时匹配到对应周完成累加,完全消除重复的判断逻辑。
写法1:兼容现有前端传参模式
如果你暂时还要保留前端传入vStart1~vEnd4的逻辑,先把周参数组装为统一集合:
// 周规则集合,后续增减周数只需要修改这里即可 var weekRules = new List<(DateTime Start, DateTime End, string PropName)> { (vStart1, vEnd1, "Week1"), (vStart2, vEnd2, "Week2"), (vStart3, vEnd3, "Week3"), (vStart4, vEnd4, "Week4") }; // 初始化统计结果,所有周初始工时为0 var weekStats = weekRules.ToDictionary(rule => rule.PropName, rule => 0m); // 数值类型和HoursWorked保持一致即可 // 单次遍历所有班次完成统计 foreach (var shift in pg) { var matchedWeek = weekRules.FirstOrDefault(rule => shift.ShiftDate >= rule.Start && shift.ShiftDate <= rule.End); if (matchedWeek != default) { weekStats[matchedWeek.PropName] += shift.HoursWorked; } } // 直接取统计结果赋值 var result = new { Week1 = weekStats["Week1"], Week2 = weekStats["Week2"], Week3 = weekStats["Week3"], Week4 = weekStats["Week4"] };
写法2:后端自动计算周范围(推荐)
完全去掉前端传周范围的逻辑,后端根据要统计的月份自动识别所有涉及的周,自动适配当月是4周还是5周的情况,不需要硬编码Week1~Week4:
// 例:统计2024年6月的班次数据 int statYear = 2024; int statMonth = 6; var monthShifts = pg.Where(s => s.ShiftDate.Year == statYear && s.ShiftDate.Month == statMonth).ToList(); // 自动提取当月所有不重复的周范围,按时间排序 var weekRules = monthShifts .Select(s => s.ShiftDate.GetWeekRange()) .DistinctBy(range => range.Start) .OrderBy(range => range.Start) .Select((range, idx) => (range.Start, range.End, PropName = $"Week{idx+1}")) .ToList(); // 后续统计逻辑和上面完全一致,自动输出Week1、Week2...对应结果 var weekStats = weekRules.ToDictionary(rule => rule.PropName, rule => 0m); foreach (var shift in monthShifts) { var shiftWeek = shift.ShiftDate.GetWeekRange(); var matchedWeek = weekRules.First(r => r.Start == shiftWeek.Start); weekStats[matchedWeek.PropName] += shift.HoursWorked; }
方案优势
- 性能更好:仅遍历一次数据源,比多次执行
Sum的遍历次数少 - 维护性强:没有重复逻辑,周数调整不需要复制粘贴判断代码
- 准确性高:周计算逻辑统一收敛在后端,避免前后端周起始规则不一致导致的统计错误
内容的提问来源于stack exchange,提问作者Rowandinho
相关产品推荐
相关产品推荐

