Automapper映射双实体到GetLoginResponse失败,报自映射缺失异常
问题背景
需要将User和UsersProvider两个实体映射到合并后的DTOGetLoginResponse,但调用时抛出映射异常,尝试多种配置后问题仍未解决。
相关代码定义
实体定义
public class User { public int UId { get; set; } public string UName { get; set; } = null!; public string UUsername { get; set; } = null!; public string UPassword { get; set; } = null!; public string? UEmail { get; set; } public int ULevel { get; set; } public int? UAddBy { get; set; } public DateTime? UAddDate { get; set; } public int? ULastEditBy { get; set; } public DateTime? ULastEditDate { get; set; } public string? UActive { get; set; } } public class UsersProvider { public int UpId { get; set; } public int UpUserId { get; set; } public int? UpProvId { get; set; } public int? UpAddBy { get; set; } public DateTime? UpAddDate { get; set; } }
合并响应DTO定义
public class GetLoginResponse { public string? Active { get; set; } public string Password { get; set; } public int UserID { get; set; } public int Level { get; set; } public string Name { get; set; } public int? RecruitmentCenterID { get; set; } }
AutoMapper映射类
public class GetLoginResponseMapper: Profile { public GetLoginResponseMapper() { //CreateMap<User, GetLoginResponse>(MemberList.Source); //CreateMap<UsersProvider, GetLoginResponse>(MemberList.Source) // .ForMember(d => d.RecruitmentCenterID, a => a.MapFrom(s => s.UpProvId)); //CreateMap<GetLoginResponse, (User, UsersProvider)>(MemberList.Destination) // .ForMember(x => x.Item1, opts => opts.MapFrom(x => x)) // .ForMember(x => x.Item2, opts => opts.MapFrom(x => x.RecruitmentCenterID)) // .ReverseMap(); CreateMap<(User user, UsersProvider usersProvider), GetLoginResponse>() .ForMember(d => d.Active, opt => opt.MapFrom(s => s.user.UActive)) .ForMember(d => d.Password, opt => opt.MapFrom(s => s.user.UPassword)) .ForMember(d => d.UserID, opt => opt.MapFrom(s => s.user.UId)) .ForMember(d => d.Level, opt => opt.MapFrom(s => s.user.ULevel)) .ForMember(d => d.Name, opt => opt.MapFrom(s => s.user.UName)) .ForMember(d => d.RecruitmentCenterID, a => a.MapFrom(s => s.usersProvider.UpProvId)); //CreateMap<User, GetLoginResponse>(); } }
查询处理Handler
public class GetLoginQueryHandler : IRequestHandler<GetLoginRequest, List<GetLoginResponse>> { private readonly ICrewApplicationDBContext _iCrewApplicationDBContext; private readonly IMapper _mapper; public GetLoginQueryHandler(ICrewApplicationDBContext iCrewApplicationDBContext, IMapper mapper) { _iCrewApplicationDBContext = iCrewApplicationDBContext; _mapper = mapper; } public Task<List<GetLoginResponse>> Handle(GetLoginRequest request, CancellationToken cancellationToken) { //return _iCrewApplicationDBContext.user.ProjectTo<GetLoginResponse>(_mapper.ConfigurationProvider).Where(a => a.UUsername == request.UserName).ToListAsync(); return (from u in _iCrewApplicationDBContext.user join up in _iCrewApplicationDBContext.usersProvider on u.UId equals up.UpUserId into loginResult from lr in loginResult.DefaultIfEmpty() where u.UName == request.UserName select new GetLoginResponse { Active = u.UActive, Password = u.UPassword, UserID = u.UId, Level = u.ULevel, Name = u.UName, RecruitmentCenterID = lr.UpProvId }) .ProjectTo<GetLoginResponse>(_mapper.ConfigurationProvider) .ToListAsync(); } }
异常信息
System.InvalidOperationException: Missing map from CrewApplication.Architecture.Application.AdminLogin.GetLoginResponse to CrewApplication.Architecture.Application.AdminLogin.GetLoginResponse. Create using CreateMap<GetLoginResponse, GetLoginResponse>.
at AutoMapper.QueryableExtensions.Impl.ProjectionBuilder.CreateProjection(ProjectionRequest& request, LetPropertyMaps letPropertyMaps)
...(省略后续堆栈信息)
解决方法
方案1:移除多余的ProjectTo调用
当前代码中已经通过LINQ查询手动构造了GetLoginResponse对象,但后续又调用了ProjectTo<GetLoginResponse>,AutoMapper会尝试将GetLoginResponse作为源类型映射到目标类型GetLoginResponse,但未配置该映射,因此抛出异常。
直接移除ProjectTo调用即可解决:
public Task<List<GetLoginResponse>> Handle(GetLoginRequest request, CancellationToken cancellationToken) { return (from u in _iCrewApplicationDBContext.user join up in _iCrewApplicationDBContext.usersProvider on u.UId equals up.UpUserId into loginResult from lr in loginResult.DefaultIfEmpty() where u.UName == request.UserName select new GetLoginResponse { Active = u.UActive, Password = u.UPassword, UserID = u.UId, Level = u.ULevel, Name = u.UName, RecruitmentCenterID = lr?.UpProvId // 添加空引用运算符,避免lr为null时出错 }) .ToListAsync(); }
方案2:使用AutoMapper完成投影(推荐)
如果希望通过AutoMapper统一管理映射规则,可修改查询逻辑,直接从联合查询的结果投影到GetLoginResponse,无需手动构造DTO:
- 调整映射配置:
public class GetLoginResponseMapper: Profile { public GetLoginResponseMapper() { CreateMap<(User user, UsersProvider usersProvider), GetLoginResponse>() .ForMember(d => d.Active, opt => opt.MapFrom(s => s.user.UActive)) .ForMember(d => d.Password, opt => opt.MapFrom(s => s.user.UPassword)) .ForMember(d => d.UserID, opt => opt.MapFrom(s => s.user.UId)) .ForMember(d => d.Level, opt => opt.MapFrom(s => s.user.ULevel)) .ForMember(d => d.Name, opt => opt.MapFrom(s => s.user.UName)) .ForMember(d => d.RecruitmentCenterID, opt => opt.MapFrom(s => s.usersProvider?.UpProvId)); // 添加空引用处理 } }
- 修改Handler中的查询逻辑:
public Task<List<GetLoginResponse>> Handle(GetLoginRequest request, CancellationToken cancellationToken) { return _iCrewApplicationDBContext.user .GroupJoin(_iCrewApplicationDBContext.usersProvider, u => u.UId, up => up.UpUserId, (user, providers) => new { user, providers }) .SelectMany(x => x.providers.DefaultIfEmpty(), (x, provider) => (user: x.user, usersProvider: provider)) .Where(x => x.user.UName == request.UserName) .ProjectTo<GetLoginResponse>(_mapper.ConfigurationProvider) .ToListAsync(); }
这样AutoMapper会根据配置的元组映射规则,直接将查询结果投影到GetLoginResponse,避免手动构造DTO的重复代码。
内容的提问来源于stack exchange,提问作者vivek

