ASP.NET Core AutoMapper映射错误:如何定位问题属性?
问题描述
在ASP.NET Core中尝试将ServicePlan实体映射到ServicePlanModel模型时,触发AutoMapperMappingException错误,提示**"缺少类型映射配置或不支持的映射"**。已配置AutoMapperProfile类,调试后仍无法定位具体出错属性,寻求排查方法。
错误栈信息
Error: AutoMapper.AutoMapperMappingException: 'Error mapping types.'
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
This exception was originally thrown at this call stack:
AutoMapper.Mapper.MapCore<TSource, TDestination>(TSource, TDestination, AutoMapper.ResolutionContext, System.Type, System.Type, AutoMapper.MemberMap)
AutoMapper.Mapper.AutoMapper.IInternalRuntimeMapper.Map<TSource, TDestination>(TSource, TDestination, AutoMapper.ResolutionContext, System.Type, System.Type, AutoMapper.MemberMap)
AutoMapper.ResolutionContext.MapInternal<TSource, TDestination>(TSource, TDestination, AutoMapper.MemberMap)
相关代码
AutoMapperProfile配置
public class AutoMapperProfile : Profile { public AutoMapperProfile() { CreateMap<Account, AccountModel>(); CreateMap<AccountModel, Account>(); CreateMap<ServicePlan, ServicePlanModel>() .ForMember(ServicePlanModel => ServicePlanModel.ServicePlanDetails, opt => opt.MapFrom(src => src.ServicePlanDetails.ToList())) .ForMember(dest => dest.Lines, opt => opt.MapFrom(src => src.NLines ?? default)) .ForMember(dest => dest.StartDate, opt => opt.MapFrom(src => src.DateStart)) .ForMember(dest => dest.EndDate, opt => opt.MapFrom(src => src.DateEnd)) .ForMember(dest => dest.MonthlyCharges, opt => opt.MapFrom(src => src.AmountMonthly)) .ForMember(dest => dest.OneTimeCharges, opt => opt.MapFrom(src => src.AmountOnetime)); } }
映射调用代码
public async Task<List<ServicePlanModel>> GetServicePlansWithDetails(long accountId) { var plan = mapper.Map<ServicePlan, ServicePlanModel>(servicePlan); }
ServicePlan实体类
public partial class ServicePlan { public ServicePlan() { ServicePlanDetails = new HashSet<ServicePlanDetail>(); } public long PlanId { get; set; } public decimal PhoneId { get; set; } public byte? NLines { get; set; } public DateTime? DateStart { get; set; } public DateTime? DateEnd { get; set; } public decimal? AmountMonthly { get; set; } public decimal? AmountOnetime { get; set; } public string? ContractType { get; set; } public string? CallerId { get; set; } public bool? Active { get; set; } public short? ResellerId { get; set; } public int? OnetimeInvoiceNo { get; set; } public bool? IsRequest { get; set; } public string? RequestType { get; set; } public string? NewValue { get; set; } public DateTime? RequestDate { get; set; } public string? RequestBy { get; set; } public bool? IsRejected { get; set; } public bool? IsApproved { get; set; } public DateTime? ApprovedWhen { get; set; } public string? ApprovedBy { get; set; } public string? Comments { get; set; } public string? AdditionalDetails { get; set; } public string? VideoTronId { get; set; } public string? Zone { get; set; } public string? PrevProvider { get; set; } public bool? Charge911Tax { get; set; } public string? ContractTerms { get; set; } public long? ContractId { get; set; } public int AllowedBandwidth { get; set; } public DateTime? ServiceStartDate { get; set; } public string? AppointmentTime { get; set; } public DateTime? AppointmentEmailSentOn { get; set; } public string? TrackingNumber { get; set; } public bool? HasNumberPort { get; set; } public DateTime? EstPortDate { get; set; } public DateTime? PortCompletedOn { get; set; } public bool? IsExGbchargesLimited { get; set; } public decimal? ExGbchagesLimitAmt { get; set; } public bool? IsPreVisitSch { get; set; } public DateTime? PreVisitSchDate { get; set; } public string? PreVisitSchTime { get; set; } public bool? MoniterBwUsage { get; set; } public int? BwWarnLimit { get; set; } public string? DslaccountId { get; set; } public string? BellGisId { get; set; } public string? DslPassword { get; set; } public bool? IsPasswordPassed { get; set; } public bool? IsDslUserPassed { get; set; } public string? CogecoId { get; set; } public int? ServiceProviderId { get; set; } public string? RogersId { get; set; } public decimal? InternetSpeed { get; set; } public bool? ModemRental { get; set; } public bool? RouterRental { get; set; } public bool? Atarental { get; set; } public string? DslaccountDomain { get; set; } [NotMapped] public virtual ICollection<ServicePlanDetail> ServicePlanDetails { get; set; } }
ServicePlanModel模型类
public class ServicePlanModel { public ServicePlanModel() { ServicePlanDiscounts = new List<ServicePlanDiscount>(); ServicePlanDetails = new List<ServicePlanDetail>(); } [Key] public Int64 PlanId { get; set; } public Int64? ContractId { get; set; } public int ResellerId { get; set; } public Int64 PhoneId { get; set; } public bool IsPhone { get; set; } public Int32? OneTimeInvoiceId { get; set; } public UInt16 Lines { get; set; } public DateTime StartDate { get; set; } public DateTime? EndDate { get; set; } public Decimal MonthlyCharges { get; set; } public Decimal OneTimeCharges { get; set; } public String ContractType { get; set; } public String CallerId { get; set; } public Boolean IsActive { get; set; } public Boolean IsRequest { get; set; } public Boolean IsRejected { get; set; } public Boolean IsApproved { get; set; } public Boolean Charge911Tax { get; set; } public DateTime ApprovedOn { get; set; } public String ApprovedBy { get; set; } public String Comments { get; set; } public String ContractTerms { get; set; } public String AdditionalDetails { get; set; } public String VideoTronId { get; set; } public String Zone { get; set; } public String PreviousProvider { get; set; } public String RequestType { get; set; } public String NewValue { get; set; } public DateTime? RequestDate { get; set; } public String RequestedBy { get; set; } //public PhoneNumber PhoneNumber { get; set; } public List<ServicePlanDetail> ServicePlanDetails { get; set; } public List<ServicePlanDiscount> ServicePlanDiscounts { get; set; } public bool IsLandLinePlan { get { if (ServicePlanDetails != null) { return ServicePlanDetails.Any(spd => new String[] { "landline-r", "landline-b", "plan_10103", "plan_10104", "plan_10105" }.Contains(spd.ServiceCode)); } return false; } } public String ServicePlanName { get { if (ServicePlanDetails != null && ServicePlanDetails.Count > 0) { var plan = ServicePlanDetails.SingleOrDefault(spd => spd.IsPlan); if (plan != null) { if (IsPhone) { return String.Format("{0} ({3}) ({1} x {2})", plan.ServiceTitle, plan.UnitPrice.ToString("N2"), plan.Quantity, PhoneId.ToString("###-###-####")); } return String.Format("{0} ({1} x {2})", plan.ServiceTitle, plan.UnitPrice.ToString("N2"), plan.Quantity); } } return String.Empty; } } public String MonthlyChargesDetails { get { if (ServicePlanDetails != null && ServicePlanDetails.Count > 0) { var items = ServicePlanDetails.Where(spd => spd.IsPlan == false & spd.UnitPrice > 0 & spd.ServiceType.Equals(Constants.SERVICE_TYPE_MONTHLY)).ToList(); var details = new StringBuilder(); foreach (var spd in items) { details.Append(String.Format("{0} ({1} x {2}) ", spd.ServiceTitle, spd.UnitPrice.ToString("N2"), spd.Quantity)); } return details.ToString(); } return String.Empty; } } public List<ServicePlanDetail> OnetimeChargesDetails { get { if (ServicePlanDetails != null && ServicePlanDetails.Count > 0) { return ServicePlanDetails.Where(spd => spd.IsPlan == false & spd.ServiceType.Equals(Constants.SERVICE_TYPE_ONETIME)).ToList(); } return null; } } public List<ServicePlanDetail> FreeServicesDetails { get { return ServicePlanDetails != null ? ServicePlanDetails.Where(s => s.UnitPrice == 0 && !s.ServiceType.Equals("month")).ToList() : null; } } public List<ServicePlanDetail> DiscountedServicesDetail { get { return ServicePlanDetails != null ? ServicePlanDetails.Where(s => s.ResellerService != null && s.UnitPrice < s.ResellerService.SuggestedPrice && !s.ServiceType.Equals("month")).ToList() : null; } } public string Modem_MAC { get; set; } public string TrackingNumber { get; set; } }
排查步骤
验证AutoMapper配置注册
确认在Program.cs(或Startup.cs)中已正确注册AutoMapper并加载配置文件:builder.Services.AddAutoMapper(typeof(AutoMapperProfile));若使用程序集扫描,确保包含
AutoMapperProfile所在的程序集。检查值类型可空性与强制类型转换
对比实体与模型的属性类型,处理可空转非可空的场景:DateStart(DateTime?)转StartDate(非可空DateTime)需添加空值兜底:.ForMember(dest => dest.StartDate, opt => opt.MapFrom(src => src.DateStart ?? DateTime.MinValue))AmountMonthly/AmountOnetime(decimal?)转非可空Decimal需处理空值:.ForMember(dest => dest.MonthlyCharges, opt => opt.MapFrom(src => src.AmountMonthly ?? 0)) .ForMember(dest => dest.OneTimeCharges, opt => opt.MapFrom(src => src.AmountOnetime ?? 0))
补充未显式映射的属性
对比两个类的属性,补充名称或类型不匹配的映射规则:.ForMember(dest => dest.IsActive, opt => opt.MapFrom(src => src.Active ?? false)) .ForMember(dest => dest.ResellerId, opt => opt.MapFrom(src => src.ResellerId ?? 0)) .ForMember(dest => dest.OneTimeInvoiceId, opt => opt.MapFrom(src => src.OnetimeInvoiceNo)) .ForMember(dest => dest.ApprovedOn, opt => opt.MapFrom(src => src.ApprovedWhen ?? DateTime.MinValue)) .ForMember(dest => dest.PreviousProvider, opt => opt.MapFrom(src => src.PrevProvider)) .ForMember(dest => dest.RequestedBy, opt => opt.MapFrom(src => src.RequestBy))启用AutoMapper配置验证
在注册时添加配置验证,启动时直接抛出未配置的属性错误:builder.Services.AddAutoMapper(cfg => { cfg.AddProfile<AutoMapperProfile>(); cfg.ConfigurationProvider.AssertConfigurationIsValid(); }, typeof(AutoMapperProfile));查看内部异常详情
捕获AutoMapperMappingException后,检查InnerException属性,获取具体出错的属性信息。检查嵌套对象映射
确认ServicePlanDetail等嵌套类型是否存在未配置的映射需求,若有需补充对应的CreateMap配置。
内容的提问来源于stack exchange,提问作者Shahreyar Butt

