AutoMapper中PascalCase与小写下划线双向映射含数字属性失败问题
看起来你遇到了AutoMapper命名约定处理嵌入数字属性时的双向映射坑,还踩了ReverseMap()搭配全局命名约定的小陷阱,我来帮你拆解问题并给出解决方案。
问题根源
全局命名约定的反向映射冲突
你第一个配置里全局设置了SourceMemberNamingConvention = PascalCase、Destination = LowerUnderscore,当用ReverseMap()时,反向映射的Source是DTO(下划线命名),但AutoMapper还是用全局的Pascal约定去解析Source成员,自然找不到对应关系,这就是反向映射值为0的核心原因。默认命名约定对数字的处理缺陷
即使你单独配置反向映射(第二个例子),AutoMapper默认的命名约定拆分逻辑对嵌入在单词中间的数字处理不够智能。比如type_1_crest转Pascal时应该是Type1Crest,但反过来Type1Crest拆分成下划线时,默认逻辑没在数字前后正确拆分,导致双向映射的名称匹配失败。
解决方案
方案一:为正向/反向映射单独配置命名约定
不要依赖全局命名约定,而是为每个映射单独指定Source和Destination的命名规则,这样ReverseMap()或者单独的反向映射都能正确识别命名风格:
var config = new MapperConfiguration(cfg => { // 正向映射:DB实体(Pascal)→ DTO(下划线) cfg.CreateMap<MyDbEntity, MyDto>() .SourceMemberNamingConvention(PascalCaseNamingConvention.Instance) .DestinationMemberNamingConvention(LowerUnderscoreNamingConvention.Instance) // 反向映射时手动反转命名约定 .ReverseMap() .SourceMemberNamingConvention(LowerUnderscoreNamingConvention.Instance) .DestinationMemberNamingConvention(PascalCaseNamingConvention.Instance); });
不过这个方案可能还解决不了数字的匹配问题,需要搭配方案二使用。
方案二:自定义命名约定处理数字拆分
AutoMapper默认的拆分正则没考虑数字作为分隔点的情况,我们可以自定义INamingConvention实现,让数字前后的字符被正确拆分:
// 自定义支持数字拆分的PascalCase命名约定 public class PascalCaseWithNumbersConvention : INamingConvention { // 正则规则:在大写前/小写大写间/字母数字间/数字字母间拆分 public Regex SplittingExpression => new Regex(@"(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[0-9])|(?<=[0-9])(?=[A-Za-z])"); public string Separator => ""; } // 自定义下划线命名约定(保持小写即可) public class LowerUnderscoreWithNumbersConvention : INamingConvention { public Regex SplittingExpression => new Regex(@"_"); public string Separator => "_"; public string Transform(string value) => value.ToLowerInvariant(); }
然后在映射中使用自定义约定:
var config = new MapperConfiguration(cfg => { cfg.CreateMap<MyDbEntity, MyDto>() .SourceMemberNamingConvention(new PascalCaseWithNumbersConvention()) .DestinationMemberNamingConvention(new LowerUnderscoreWithNumbersConvention()) .ReverseMap() .SourceMemberNamingConvention(new LowerUnderscoreWithNumbersConvention()) .DestinationMemberNamingConvention(new PascalCaseWithNumbersConvention()); }); config.AssertConfigurationIsValid(); // 现在不会报错了 var mapper = config.CreateMapper(); // 测试双向映射 var dto = mapper.Map<MyDto>(new MyDbEntity { EquipCrestSlotType1CrestId1 = 10 }); Console.WriteLine(dto.equip_crest_slot_type_1_crest_id_1); // 输出10 var dbEntity = mapper.Map<MyDbEntity>(dto); Console.WriteLine(dbEntity.EquipCrestSlotType1CrestId1); // 输出10
额外提示
如果你不想自定义命名约定,也可以用ForMember手动指定映射,但你提到不想手动处理,所以上面的方案更适合批量处理所有带数字的属性。
备注:内容来源于stack exchange,提问作者lordnoob

