.NET中转换数据库返回值格式并映射至ViewModel的问题
需求描述
将包含ID、Username、HistorDay、Clock字段的Model,映射到包含ID、Username、HistorDay、check-in、check-out字段的ViewModel。需按Username和HistorDay分组,把Clock列表中**奇数索引(index%2!=0)**的值存入check-in,**偶数索引(index%2==0)**的值存入check-out。
第一次尝试及问题
代码
var Filter = _context.Attendances .GroupBy(x => new { x.Username, x.HistorDay }) .Select(x => new { Username = x.Key.Username, HistorDay = x.Key.HistorDay, In = x.Select(c => c.Clock).Where((q, w) => w % 2 != 0), Out = x.Select(c => c.Clock).Where((q, w) => w % 2 == 0), });
异常
System.Collections.Generic.IEnumerable1[Eams.Models.Attendance]`
原因
EF Core无法将带索引参数的Where((q,w)=>...)表达式转换为SQL语句,直接在IQueryable上执行这类操作会抛出转换异常。
第二次尝试及输出
代码
var rawData = _context.Attandances .Select(a => new { a.Username, a.HistorDay, a.Clock }) .ToList(); var result = rawData .GroupBy(x => new { x.Username, x.HistorDay }) .Select(x => new { Username = x.Key.Username, HistorDay = x.Key.HistorDay, In = x.Select(c => c.Clock).Where((q, w) => w % 2 != 0).ToList(), Out = x.Select(c => c.Clock).Where((q, w) => w % 2 == 0).ToList(), }); foreach (var item in result) { Debug.WriteLine($"ALL {item}"); }
控制台输出
ALL { Username = 43091, HistorDay = 6/23/2023 12:00:00 AM, In = System.Collections.Generic.List
1[System.TimeSpan], Out = System.Collections.Generic.List1[System.TimeSpan] }
说明
这段代码逻辑本身是正确的:先调用ToList()将数据加载到内存,再用LINQ to Objects处理索引筛选,避免了EF Core的SQL转换问题。但控制台输出显示集合类型而非具体值,是因为直接打印匿名对象时,集合只会输出类型名称,需要遍历集合才能看到具体元素。
优化方案(映射到ViewModel)
首先定义ViewModel:
public class AttendanceViewModel { public int ID { get; set; } public string Username { get; set; } public DateTime HistorDay { get; set; } public List<TimeSpan> CheckIn { get; set; } public List<TimeSpan> CheckOut { get; set; } }
修改代码完成映射并输出具体值:
var rawData = _context.Attendances .Select(a => new { a.ID, a.Username, a.HistorDay, a.Clock }) .ToList(); var viewModelList = rawData .GroupBy(x => new { x.Username, x.HistorDay }) .Select(g => new AttendanceViewModel { ID = g.First().ID, // 取分组内第一条数据的ID,可根据业务规则调整 Username = g.Key.Username, HistorDay = g.Key.HistorDay, CheckIn = g.Select((c, idx) => (c.Clock, idx)) .Where(t => t.idx % 2 != 0) .Select(t => t.Clock) .ToList(), CheckOut = g.Select((c, idx) => (c.Clock, idx)) .Where(t => t.idx % 2 == 0) .Select(t => t.Clock) .ToList() }) .ToList(); // 打印具体数据示例 foreach (var vm in viewModelList) { Debug.WriteLine($"用户名: {vm.Username}, 日期: {vm.HistorDay:yyyy-MM-dd}"); Debug.WriteLine("签到记录:"); foreach (var inTime in vm.CheckIn) { Debug.WriteLine($" {inTime}"); } Debug.WriteLine("签退记录:"); foreach (var outTime in vm.CheckOut) { Debug.WriteLine($" {outTime}"); } }
关键要点
- 必须先通过
ToList()将数据库数据加载到内存,再使用LINQ to Objects处理带索引的筛选逻辑,避免EF Core的SQL转换错误。 - 映射ViewModel时,
ID字段需根据业务规则处理(示例中取分组第一条数据的ID)。 - 打印集合数据时需遍历元素,才能输出具体值而非集合类型名称。
内容的提问来源于stack exchange,提问作者OmarNology

