首页 > 基础资料 博客日记
AutoMapper三板斧:值转换器、条件映射、自定义解析器,复杂映射不再愁
2026-04-17 08:30:02基础资料围观1次
大家好,我是刚子。
写了几年.NET代码,AutoMapper这玩意儿没少用。
刚开始接触的时候就觉得——“哎呦,这东西太爽了!”不用再一行一行手动赋值,一个Map全搞定。但用着用着就发现,光会CreateMap可不够。遇到复杂情况,简单配置根本搞不定,要么报错,要么映射出来的东西不是你要的。
今天刚子就跟你聊聊,我这些年踩坑踩出来的3个高级配置技巧。保证小白也能看懂。
技巧一:值转换器——搞定“类型对不上”
啥时候用这个?就是源属性和目标属性类型不一样的时候。
举个例子,你数据库里存的是decimal类型的金额,比如123.45。但前端要展示带美元符号的字符串,比如"$123.45"。你要是不用转换器,就得在DTO里单独搞个字符串属性,或者在映射之后手动格式化。麻烦不麻烦?
用值转换器就简单了:
public class CurrencyFormatter : IValueConverter<decimal, string>
{
public string Convert(decimal source, ResolutionContext context)
{
return source.ToString("C2"); // 输出 $123.45
}
}
// 配置
cfg.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.Amount,
opt => opt.ConvertUsing(new CurrencyFormatter()));
每次碰到Order里的Amount要映射到OrderDto的Amount,AutoMapper就会自动走这个转换逻辑。
划重点:值转换器的好处是能重复用。你可以在好几个地方用同一个转换器,不用每个地方都写一遍格式化代码。但有个小坑:值转换器只在普通映射时生效,如果你用EF Core直接从数据库投影(ProjectTo),它不干活。这个记一下就行。
技巧二:条件映射——想清楚再动手
这个技巧特别实用。简单说就是:满足条件才映射,不满足就跳过。
举个例子,你有一个用户实体,年龄是int类型,但目标DTO里年龄是uint(无符号整数,就是不能为负数)。负数不能转成无符号整数,对吧?这时候就可以加个条件:
cfg.CreateMap<User, UserDto>()
.ForMember(dest => dest.Age, opt => opt.Condition(src => src.Age >= 0));
只有源年龄大于等于0时,才映射。
条件映射里还有一对孪生兄弟:Condition和PreCondition,它俩的区别就是谁先跑。PreCondition跑得更早,在源值被拿出来之前就执行。PreCondition主要是为了省时间——如果你的源值解析非常耗时(比如要查数据库),可以在PreCondition里先判断是否满足条件,不满足就直接跳过,不用浪费时间。
划重点:条件映射别到处用。偶尔用一两个没问题,但如果你发现到处都在写Condition,多半是你的模型设计本身有问题,回去改模型比补条件更省事。
技巧三:自定义值解析器——复杂逻辑的归宿
值转换器适合解决“类型对不上”这种一对一的问题。但有些场景更复杂——比如你需要从源对象的好几个地方拿信息,拼成一个目标属性;或者逻辑太复杂,一行代码写不下。
这时候就要上自定义值解析器了。
举个例子,你要把Person里的FirstName(名)和LastName(姓)拼成FullName(全名):
public class FullNameResolver : IValueResolver<Person, PersonDto, string>
{
public string Resolve(Person source, PersonDto destination,
string destMember, ResolutionContext context)
{
return $"{source.FirstName} {source.LastName}";
}
}
// 配置
cfg.CreateMap<Person, PersonDto>()
.ForMember(dest => dest.FullName,
opt => opt.MapFrom<FullNameResolver>());
解析器的Resolve方法能拿到源对象、目标对象、目标成员名和上下文信息。这意味着你可以访问目标对象的其他属性来做复杂判断。
划重点:自定义解析器还有个隐藏好处——支持依赖注入。如果你的解析器里需要用到数据库、缓存啥的,可以直接通过构造函数传进来。但注意要把Mapper配置成单例模式,不然每个请求都建个新的,服务器扛不住。
一个真实案例:三个技巧一起上
说了这么多理论,刚子给你来个真实的例子。
有这样一个需求:订单列表页面,后端返回的Order实体里存的是decimal金额和DateTime时间,但前端要展示带货币符号的字符串和格式化的日期。而且如果订单是取消状态,金额那一栏直接显示“已取消”而不是金额数字,金额超过1万的还要加个特殊标识。
如果不用高级配置,只能在DTO里单独搞字符串属性,然后在Service层手写逻辑。代码又多又难维护,每次改需求都得翻来覆去改好几个地方。
用AutoMapper的高级技巧,一个配置文件全搞定:
public class OrderProfile : Profile
{
public OrderProfile()
{
CreateMap<Order, OrderDto>()
// 金额映射:走转换器 + 条件判断 + 解析器组合
.ForMember(dest => dest.FormattedAmount, opt =>
{
// 条件:订单状态是取消的话,跳过金额转换逻辑
opt.Condition(src => src.Status != OrderStatus.Cancelled);
// 再用转换器把 decimal 变成 $123.45 格式
opt.ConvertUsing(new CurrencyFormatter());
})
// 日期映射:用值转换器搞定格式
.ForMember(dest => dest.FormattedCreateTime,
opt => opt.ConvertUsing(new DateTimeFormatter("yyyy-MM-dd HH:mm")))
// 超万金额标识:用自定义解析器,内部判断金额>10000时追加标识
.ForMember(dest => dest.AmountDisplay,
opt => opt.MapFrom<AmountDisplayResolver>());
}
}
你看,原来要在Service层写一大坨逻辑才能搞定的事儿,现在全收进Profile里了。业务层代码干干净净,就一行_mapper.Map<OrderDto>(order)。
划重点:把映射逻辑收进Profile里,还有一个额外的好处——单元测试特别好写。你只需要测试Profile的配置对不对,不用把Service层也扯进来,测试用例又少又干净。
最后刚子想说
AutoMapper这个工具,入门简单,想玩溜真得花点功夫。
今天讲的这三个技巧——值转换器、条件映射、自定义值解析器——都是我这些年实战总结出来的。学会了,你就不用再被那些复杂的映射场景折磨了。
记住:能用配置解决的问题,就别写到业务代码里。
如果你觉得这篇文章有用,点个赞、转给还在手写对象映射的兄弟。
我是刚子,一个写了六年代码的.NET程序员。咱们下回见!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签:
相关文章
最新发布
- 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差
- 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里实现可靠Flash IAP的方法
- Atcoder - abc453_d Go Straight
- Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量
- AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布
- 在后台服务中使用 Scoped 服务,为什么总是报错?
- H200 安装驱动并使用sglang启动模型
- 玩转控件:封装个带图片的Label控件
- Oracle数据库SCN推进技术详解与实践指南
- [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型

