AutoMapper映射时抛出LINQ无法构造复杂类型异常的排查
我创建了如下AutoMapper配置:
CreateMap<Records_Log, Records_LogDto>() .ForMember(dest => dest.UserName, opt => { opt.ResolveUsing(d => d.User.UserName); });
对应实体类定义如下:
Records_Log 实体
public class Records_Log { public int Id { get; set; } public string Table_name { get; set; } [ForeignKey("Records")] public int RecordsId { get; set; } public Records Records { get; set; } public int RecordId { get; set; } public string OldValue { get; set; } public string NewValue { get; set; } [ForeignKey("User")] public string UserId { get; set; } public ApplicationUser User { get; set; } public DateTime ChangeDate { get; set; } public string Action { get; set; } }
Records_LogDto 实体
public class Records_LogDto { public int Id { get; set; } public string Table_name { get; set; } public int RecordsId { get; set; } public Records Records { get; set; } public int RecordId { get; set; } public string OldValue { get; set; } public string NewValue { get; set; } public string UserId { get; set; } public string UserName { get; set; } public string Action { get; set; } public DateTime ChangeDate { get; set; } }
Records 实体
public class Records { public int Id { get; set; } public string SerialName { get; set; } public string RecordsName { get; set; } public string Url { get; set; } public string AttachmentPath { get; set; } public string ImagePath { get; set; } public string RecordsNameModified { get; set; } public bool isDeleted { get; set; } public bool isRead { get; set; } public bool IsHidden { get; set; } public bool MarkAsImportant { get; set; } public string ImportantComments { get; set; } public int subTypeID { get; set; } public virtual ICollection<Records_Rate> Records_Rate { get; set; } }
编写的LINQ查询代码如下:
var query = (from st in db.Records_State join sl in db.Records_Log on st.Records.Id equals sl.RecordsId where st.Records.isDeleted && sl.Table_name == nameof(Records) && sl.Action == "Deleted" && sl.NewValue == "Top" && (string.IsNullOrEmpty(recordsName) || sl.Records.RecordsName.Contains(recordsName) || sl.Records.RecordsNameModified.Contains(recordsName)) && (fromDate == null || sl.ChangeDate >= fromDate) && (toDate == null || sl.ChangeDate <= toDate) orderby sl.ChangeDate descending select new Records_Log { Records= st.Records, ChangeDate= sl.ChangeDate }) .AsQueryable(); query = from p in query group p by p.Records.Id into pGroup orderby pGroup.Key select pGroup.FirstOrDefault(); var result = query.Skip((pageNumber - 1) * pageSize) .Take(pageSize) .OrderByDescending(st => st.Id); var dto = Mapper.Map<List<Records_LogDto>>(result);
执行最后一行var dto = Mapper.Map<List<Records_LogDto>>(result);时,抛出以下异常:
Error mapping types.
Mapping types:
DbQuery1 -> List1
System.Data.Entity.Infrastructure.DbQuery1[[PortalBackEndAPI.Models.Records_Log, PortalBackEndAPI.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] -> System.Collections.Generic.List1[[PortalBackEndAPI.Dto.Records_LogDto, PortalBackEndAPI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"
以及:
The entity or complex type 'PortalBackEndAPI.Models.Records_Log' cannot be constructed in a LINQ to Entities query
- LINQ to Entities 限制构造实体:EF不允许在LINQ查询中直接通过
new Records_Log { ... }构造EF实体类实例,这种操作无法被转换为SQL语句,这是核心触发点。 - 延迟加载的映射冲突:
result是未执行的DbQuery对象,AutoMapper尝试直接映射查询对象而非实际数据集合,导致类型不匹配错误。
方法一:用匿名类承接查询结果,直接映射到DTO
避免在查询中构造实体类,改用匿名类获取所需字段,再转换为DTO:
var query = (from st in db.Records_State join sl in db.Records_Log on st.Records.Id equals sl.RecordsId where st.Records.isDeleted && sl.Table_name == nameof(Records) && sl.Action == "Deleted" && sl.NewValue == "Top" && (string.IsNullOrEmpty(recordsName) || sl.Records.RecordsName.Contains(recordsName) || sl.Records.RecordsNameModified.Contains(recordsName)) && (fromDate == null || sl.ChangeDate >= fromDate) && (toDate == null || sl.ChangeDate <= toDate) orderby sl.ChangeDate descending // 用匿名类承接所有需要的字段 select new { sl.Id, sl.Table_name, sl.RecordsId, Records = st.Records, sl.RecordId, sl.OldValue, sl.NewValue, sl.UserId, sl.Action, sl.ChangeDate, UserName = sl.User.UserName }) .AsQueryable(); // 分组逻辑保持不变 query = from p in query group p by p.Records.Id into pGroup orderby pGroup.Key select pGroup.FirstOrDefault(); // 执行查询并加载到内存 var resultList = query.Skip((pageNumber - 1) * pageSize) .Take(pageSize) .OrderByDescending(st => st.Id) .ToList(); // 直接构造DTO集合 var dto = resultList.Select(item => new Records_LogDto { Id = item.Id, Table_name = item.Table_name, RecordsId = item.RecordsId, Records = item.Records, RecordId = item.RecordId, OldValue = item.OldValue, NewValue = item.NewValue, UserId = item.UserId, UserName = item.UserName, Action = item.Action, ChangeDate = item.ChangeDate }).ToList();
方法二:先加载实体到内存再处理
如果需要保留实体类操作,先将查询结果加载到内存,再进行分组和映射:
// 查询完整数据并预加载导航属性,加载到内存 var rawData = (from st in db.Records_State join sl in db.Records_Log on st.Records.Id equals sl.RecordsId where st.Records.isDeleted && sl.Table_name == nameof(Records) && sl.Action == "Deleted" && sl.NewValue == "Top" && (string.IsNullOrEmpty(recordsName) || sl.Records.RecordsName.Contains(recordsName) || sl.Records.RecordsNameModified.Contains(recordsName)) && (fromDate == null || sl.ChangeDate >= fromDate) && (toDate == null || sl.ChangeDate <= toDate) orderby sl.ChangeDate descending select sl) .Include(l => l.Records) // 预加载Records .Include(l => l.User) // 预加载User,避免延迟加载问题 .ToList(); // 执行查询,加载到内存 // 内存中分组分页 var groupedData = rawData.GroupBy(p => p.Records.Id) .OrderBy(g => g.Key) .Select(g => g.FirstOrDefault()) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .OrderByDescending(st => st.Id) .ToList(); // 正常映射 var dto = Mapper.Map<List<Records_LogDto>>(groupedData);
额外优化建议
- 避免在DTO中直接包含
Records实体,建议创建RecordsDto并配置AutoMapper嵌套映射,降低实体耦合。 - 始终用
Include预加载所需导航属性,避免映射时触发延迟加载导致额外查询或报错。
内容的提问来源于stack exchange,提问作者testy

