如何向AutoMapper Profile传参?映射NdtType遇异常求助
父表LineJoint与子表NdtStatus是一对多关联,子表记录可通过JointId和NdtId唯一确定。调用仓储方法GetLineJointsForNdtSelectionAsync时,需要通过这两个ID过滤数据——JointId可在解析器中获取,NdtId需从仓储方法传入。
尝试用以下代码传递参数:
.ProjectTo<Models.Output.Piping.LineJoints.LineJointForNdtSelection>( _mapper.ConfigurationProvider, new { ndtId = query.NdtId })
但传入解析器的NdtId是空GUID。之后改用(Guid)context.Items["ndtId"]在Profile类中获取参数,抛出异常:
Unable to create a map expression from . (Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint) to String.NdtType (System.String) Mapping types: LineJoint -> LineJointForNdtSelection Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint -> Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection Type Map configuration: LineJoint -> LineJointForNdtSelection Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint -> Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection Destination Member: NdtType.
父表实体
public class LineJoint : EntityBase { public const int JointNoIndexMaxLength = 5; public const int JointNoMaxLength = 20; public LineJoint() { } public LineJoint(Guid? userId) : base(userId) { } public Guid? ContractorId { get; set; } public Guid? LineSheetId { get; set; } public string? JointNo { get; set; } public Guid? ScheduleId { get; set; } public Guid? ConnectionTypeId { get; set; } public Guid? EndStatusId { get; set; } public Guid? EndTypeId { get; set; } public Guid? JointTypeId { get; set; } public Guid? LocationId { get; set; } public Guid? WeldTypeId { get; set; } public int Revision { get; set; } public Guid? TestPackageId { get; set; } // Navigation public Domain.Entities.Project.Contractors.Contractor? Contractor { get; set; } public Domain.Entities.Piping.LineSheets.LineSheet? LineSheet { get; set; } public Domain.Entities.Piping.ConnectionTypes.ConnectionType? ConnectionType { get; set; } public Domain.Entities.Piping.EndTypes.EndType? EndType { get; set; } public Domain.Entities.Piping.EndStatuses.EndStatus? EndStatus { get; set; } public Domain.Entities.Piping.JointTypes.JointType? JointType { get; set; } public Domain.Entities.Piping.Locations.Location? Location { get; set; } public Domain.Entities.Piping.WeldTypes.WeldType? WeldType { get; set; } public Domain.Entities.TestPackage.TestPackages.TestPackage? TestPackage { get; set; } public Domain.Entities.Piping.Schedules.Schedule? Schedule { get; set; } public List<Domain.Entities.Piping.NdtStatuses.NdtStatus>? NdtStatuses { get; set; } }
子表实体
public class NdtStatus : Entities.EntityBase { public const int NDTNoMaxLength = 50; public NdtStatus() { } public NdtStatus(Guid? userId) : base(userId) { } public Guid? LineJointId { get; set; } public Guid? NdtId { get; set; } public bool Required { get; set; } public bool Exempted { get; set; } public string? NdtNo { get; set; } public Guid? NdtTypeId { get; set; } // Navigation public Domain.Entities.Piping.LineJoints.LineJoint? LineJoint { get; set; } public Domain.Entities.Piping.Ndts.Ndt? Ndt { get; set; } public Domain.Entities.Piping.NdtTypes.NdtType? NdtType { get; set; } }
输出模型
public class LineJointForNdtSelection : OutputModelEssentials { // From parent table public Guid JointId { get; set; } public string? Contractor { get; set; } public string? SheetNo { get; set; } public string? JointNo { get; set; } public string? ConnectionType { get; set; } public string? EndStatus { get; set; } public string? EndType { get; set; } public string? JointType { get; set; } public string? Location { get; set; } public string? PackageNo { get; set; } // From child table public string? Ndt { get; set; } public string? NdtType { get; set; } public bool Required { get; set; } public bool Exempted { get; set; } public string? NdtNo { get; set; } public string? RequestNo { get; set; } public string? ReportNo { get; set; } public string? Result { get; set; } }
原AutoMapper Profile类
public class LineJointForNdtSelection : AutoMapper.Profile { public LineJointForNdtSelection() { CreateMap< Domain.Entities.Piping.LineJoints.LineJoint, Models.Output.Piping.LineJoints.LineJointForNdtSelection>() // From parent table .ForMember(x => x.JointId, x => x.MapFrom(x => x.Id)) .ForMember(x => x.Contractor, x => x.MapFrom(x => x.Contractor.Name)) .ForMember(x => x.SheetNo, x => x.MapFrom(x => x.LineSheet.SheetNo)) .ForMember(x => x.ConnectionType, x => x.MapFrom(x => x.ConnectionType.Name)) .ForMember(x => x.EndStatus, x => x.MapFrom(x => x.EndStatus.Name)) .ForMember(x => x.EndType, x => x.MapFrom(x => x.EndType.Name)) .ForMember(x => x.JointType, x => x.MapFrom(x => x.JointType.Name)) .ForMember(x => x.Location, x => x.MapFrom(x => x.Location.Name)) .ForMember(x => x.PackageNo, x => x.MapFrom(x => x.TestPackage.TestPackageNo)) // From child table .ForMember(x => x.NdtType, opt => opt.MapFrom((src, dest, destMember, context) => new Profiles.Piping.NdtStatuses.Resolvers.NdtTypeResolver((Guid)context.Items["ndtId"]) .Resolve(src, dest, destMember, context))); ; } }
原自定义解析器
public class NdtTypeResolver : IValueResolver< Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint, Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection, string?> { private Guid? _ndtId; public NdtTypeResolver() { } public NdtTypeResolver(Guid? ndtId = null) { _ndtId = ndtId; } public string? Resolve( Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint source, Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection destination, string? destMember, ResolutionContext context) { return source.NdtStatuses?.FirstOrDefault(c => c.LineJointId == source.Id && c.NdtId == _ndtId)?.NdtType?.Name ?? string.Empty; } }
原仓储方法
public async Task<List<Models.Output.Piping.LineJoints.LineJointForNdtSelection>> GetLineJointsForNdtSelectionAsync( Models.Input.Queries.Piping.LineJoints.GetLineJointsForNdtSelection query) { var result = await _dbContext.LineJoints .Where(x => x.LineSheet.LineNumberId == query.LineNumberId) .ProjectTo<Models.Output.Piping.LineJoints.LineJointForNdtSelection>( _mapper.ConfigurationProvider, new { ndtId = query.NdtId }) .AsNoTracking() .ToListAsync() ; return result; }
问题核心是手动实例化解析器并调用Resolve方法的写法无法被AutoMapper的表达式树解析(ProjectTo依赖LINQ表达式转换为SQL),同时参数传递逻辑存在冲突。以下是正确实现步骤:
1. 调整自定义解析器,直接从上下文读取参数
移除构造函数传参逻辑,在Resolve方法中从context.Items获取NdtId:
public class NdtTypeResolver : IValueResolver< Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint, Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection, string?> { public string? Resolve( Ces.Caspian.Domain.Entities.Piping.LineJoints.LineJoint source, Ces.Caspian.Models.Output.Piping.LineJoints.LineJointForNdtSelection destination, string? destMember, ResolutionContext context) { // 校验参数存在性与类型 if (!context.Items.TryGetValue("ndtId", out var ndtIdObj) || ndtIdObj is not Guid ndtId) return string.Empty; return source.NdtStatuses?.FirstOrDefault(c => c.LineJointId == source.Id && c.NdtId == ndtId)?.NdtType?.Name ?? string.Empty; } }
2. 修改AutoMapper Profile,正确注册解析器
- 避免Profile类名与输出模型重名,改为
LineJointForNdtSelectionProfile - 使用
ResolveUsing指定解析器类型,让AutoMapper自动管理上下文传递:
public class LineJointForNdtSelectionProfile : AutoMapper.Profile { public LineJointForNdtSelectionProfile() { CreateMap< Domain.Entities.Piping.LineJoints.LineJoint, Models.Output.Piping.LineJoints.LineJointForNdtSelection>() // 父表字段映射保留不变 .ForMember(x => x.JointId, x => x.MapFrom(x => x.Id)) .ForMember(x => x.Contractor, x => x.MapFrom(x => x.Contractor.Name)) .ForMember(x => x.SheetNo, x => x.MapFrom(x => x.LineSheet.SheetNo)) .ForMember(x => x.ConnectionType, x => x.MapFrom(x => x.ConnectionType.Name)) .ForMember(x => x.EndStatus, x => x.MapFrom(x => x.EndStatus.Name)) .ForMember(x => x.EndType, x => x.MapFrom(x => x.EndType.Name)) .ForMember(x => x.JointType, x => x.MapFrom(x => x.JointType.Name)) .ForMember(x => x.Location, x => x.MapFrom(x => x.Location.Name)) .ForMember(x => x.PackageNo, x => x.MapFrom(x => x.TestPackage.TestPackageNo)) // 子表字段映射:指定解析器类型 .ForMember(x => x.NdtType, opt => opt.ResolveUsing<NdtTypeResolver>()); } }
3. 仓储方法中完善参数传递与校验
保持ProjectTo的参数传递逻辑,同时增加参数非空校验:
public async Task<List<Models.Output.Piping.LineJoints.LineJointForNdtSelection>> GetLineJointsForNdtSelectionAsync( Models.Input.Queries.Piping.LineJoints.GetLineJointsForNdtSelection query) { // 提前校验参数,避免空GUID传入 if (query.NdtId == Guid.Empty) throw new ArgumentException("NdtId 不能为空", nameof(query.NdtId)); var result = await _dbContext.LineJoints .Include(x => x.NdtStatuses) // 若EF需要显式加载关联数据则添加,ProjectTo可自动处理时可省略 .Where(x => x.LineSheet.LineNumberId == query.LineNumberId) .ProjectTo<Models.Output.Piping.LineJoints.LineJointForNdtSelection>( _mapper.ConfigurationProvider, new { ndtId = query.NdtId }) .AsNoTracking() .ToListAsync(); return result; }
4. 其他子表字段的映射扩展
如果需要映射Required、Exempted等其他子表字段,可复用上下文参数过滤逻辑:
.ForMember(x => x.Required, opt => opt.MapFrom((src, dest, _, context) => src.NdtStatuses?.FirstOrDefault(c => c.LineJointId == src.Id && c.NdtId == (Guid)context.Items["ndtId"])?.Required ?? false))
ProjectTo依赖LINQ表达式树转换,禁止手动调用解析器的Resolve方法,必须让AutoMapper管理解析器生命周期与上下文传递。- Profile类名不要与输出模型同名,否则会导致AutoMapper配置冲突。
- 从
context.Items读取参数时必须做类型校验,避免空值或类型转换异常。
内容的提问来源于stack exchange,提问作者Hadi Mazareei

