为何目标类含参数化构造函数时AutoMapper无法将字符串转布尔?
我有一个带参数化构造函数的Destination类,尝试使用AutoMapper将Source对象映射到Destination。Id的映射正常,但InStore的自定义映射似乎无法正常工作。以下是我的代码:
using AutoMapper; var config = new MapperConfiguration(cfg => { cfg.CreateMap<Source, Destination>() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => int.Parse(src.Id))) .ForMember(dest => dest.InStore, opt => opt.MapFrom(src => src.InStore == "1")); }); var mapper = config.CreateMapper(); var source = new Source { Id = "123", InStore = "1" }; var destination = mapper.Map<Destination>(source); Console.WriteLine($"Id: {destination.Id}, InStore: {destination.InStore}"); public class Source { public string Id { get; set; } public string InStore { get; set; } } public class Destination { public int Id { get; private set; } public bool InStore { get; private set; } public Destination(int id, bool inStore) { Id = id; InStore = inStore; } }
抛出异常:
AutoMapper.AutoMapperMappingException: 'Error mapping types.'
FormatException: String '1' was not recognized as a valid Boolean.
当我移除Destination的参数化构造函数时,映射能正常工作;当设置var source = new Source { Id = "123", InStore = "true" };时,映射也能正常工作。这些现象让我困惑:为何在存在构造函数且InStore设为"1"时映射会失败?
生成的表达式树如下:
(Source src, Destination dest, ResolutionContext ctxt) => { Destination typeMapDestination; return src == default(Source) ? default(Destination) : ( typeMapDestination = dest ?? new Destination(( string resolvedValue, resolvedValue = false || src == null ? default(string) : src.Id, Convert.ToInt32(resolvedValue) ), ( string resolvedValue, resolvedValue = false || src == null ? default(string) : src.InStore, Convert.ToBoolean(resolvedValue) )), if (dest != null) { try { ( string resolvedValue, int propertyValue, resolvedValue = false || src == null ? default(string) : src.Id, propertyValue = Convert.ToInt32(resolvedValue), typeMapDestination.Id = propertyValue ) } catch (Exception ex) { throw new AutoMapperMappingException("Error mapping types.", ex, #TypePair, #TypeMap, #PropertyMap); } }, if (dest != null) { try { ( string resolvedValue, bool propertyValue, resolvedValue = false || src == null ? default(string) : src.InStore, propertyValue = Convert.ToBoolean(resolvedValue), typeMapDestination.InStore = propertyValue ) } catch (Exception ex) { throw new AutoMapperMappingException("Error mapping types.", ex, #TypePair, #TypeMap, #PropertyMap); } }, try { ( string resolvedValue, resolvedValue = try { false || src == null ? default(string) : src.Id; } catch (NullReferenceException) { default(string); } catch (ArgumentNullException) { default(string); }, typeMapDestination.Name = resolvedValue ) } catch (Exception ex) { throw new AutoMapperMappingException("Error mapping types.", ex, #TypePair, #TypeMap, #PropertyMap); }, typeMapDestination ); }
看起来ForMember未按预期工作,我该如何修改才能实现预期结果?
问题原因
AutoMapper在处理带参数化构造函数的目标类时,优先通过构造函数参数完成实例初始化,而非直接映射属性。你的Destination构造函数参数inStore会被AutoMapper自动关联到Source的InStore属性,但它默认用Convert.ToBoolean(src.InStore)转换字符串——而Convert.ToBoolean仅识别"True"/"False"(大小写无关),无法解析"1"/"0",因此抛出格式异常。
你配置的ForMember规则仅在属性赋值阶段生效,但构造函数初始化时已经触发异常,根本无法进入后续步骤。
解决方案
方案1:配置构造函数参数的映射规则
直接指定构造函数中id和inStore参数的映射逻辑,覆盖AutoMapper的默认行为:
var config = new MapperConfiguration(cfg => { cfg.CreateMap<Source, Destination>() .ForCtorParam("id", opt => opt.MapFrom(src => int.Parse(src.Id))) .ForCtorParam("inStore", opt => opt.MapFrom(src => src.InStore == "1")); });
方案2:使用ConstructUsing自定义对象创建逻辑
手动接管Destination实例的创建过程,完全自定义构造逻辑:
var config = new MapperConfiguration(cfg => { cfg.CreateMap<Source, Destination>() .ConstructUsing(src => new Destination(int.Parse(src.Id), src.InStore == "1")); });
两种方案均可解决问题:若构造函数参数较多,ForCtorParam更灵活;若构造逻辑简单,ConstructUsing更直接直观。
内容的提问来源于stack exchange,提问作者TonWin

