AutoMapper三板斧:值转换器、条件映射、自定义解析器,复杂映射不再愁

大家好,我是刚子。

写了几年.NET代码,AutoMapper这玩意儿没少用。

刚开始接触的时候就觉得------"哎呦,这东西太爽了!"不用再一行一行手动赋值,一个Map全搞定。但用着用着就发现,光会CreateMap可不够。遇到复杂情况,简单配置根本搞不定,要么报错,要么映射出来的东西不是你要的。

今天刚子就跟你聊聊,我这些年踩坑踩出来的3个高级配置技巧。保证小白也能看懂。


技巧一:值转换器------搞定"类型对不上"

啥时候用这个?就是源属性和目标属性类型不一样的时候。

举个例子,你数据库里存的是decimal类型的金额,比如123.45。但前端要展示带美元符号的字符串,比如"$123.45"。你要是不用转换器,就得在DTO里单独搞个字符串属性,或者在映射之后手动格式化。麻烦不麻烦?

用值转换器就简单了:

csharp 复制代码
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要映射到OrderDtoAmount,AutoMapper就会自动走这个转换逻辑。

划重点 :值转换器的好处是能重复用。你可以在好几个地方用同一个转换器,不用每个地方都写一遍格式化代码。但有个小坑:值转换器只在普通映射时生效,如果你用EF Core直接从数据库投影(ProjectTo),它不干活。这个记一下就行。


技巧二:条件映射------想清楚再动手

这个技巧特别实用。简单说就是:满足条件才映射,不满足就跳过

举个例子,你有一个用户实体,年龄是int类型,但目标DTO里年龄是uint(无符号整数,就是不能为负数)。负数不能转成无符号整数,对吧?这时候就可以加个条件:

csharp 复制代码
cfg.CreateMap<User, UserDto>()
    .ForMember(dest => dest.Age, opt => opt.Condition(src => src.Age >= 0));

只有源年龄大于等于0时,才映射。

条件映射里还有一对孪生兄弟:ConditionPreCondition,它俩的区别就是谁先跑。PreCondition跑得更早,在源值被拿出来之前就执行。PreCondition主要是为了省时间------如果你的源值解析非常耗时(比如要查数据库),可以在PreCondition里先判断是否满足条件,不满足就直接跳过,不用浪费时间。

划重点 :条件映射别到处用。偶尔用一两个没问题,但如果你发现到处都在写Condition,多半是你的模型设计本身有问题,回去改模型比补条件更省事。


技巧三:自定义值解析器------复杂逻辑的归宿

值转换器适合解决"类型对不上"这种一对一的问题。但有些场景更复杂------比如你需要从源对象的好几个地方拿信息,拼成一个目标属性;或者逻辑太复杂,一行代码写不下。

这时候就要上自定义值解析器了。

举个例子,你要把Person里的FirstName(名)和LastName(姓)拼成FullName(全名):

csharp 复制代码
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的高级技巧,一个配置文件全搞定:

csharp 复制代码
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程序员。咱们下回见!

相关推荐
.NET修仙日记2 小时前
2026 .NET 面试八股文:高频题 + 答案 + 原理(进阶核心篇)
面试·职场和发展·c#·.net·.net core·微软技术·webapi
她说彩礼65万11 小时前
C# 实现简单的日志打印
开发语言·javascript·c#
绿浪198411 小时前
c# 中结构体 的定义字符串字段(性能优化)
开发语言·c#
唐青枫12 小时前
C#.NET ObjectPool 深入解析:对象复用、池化策略与使用边界
c#·.net
kaikaile199515 小时前
C# 文件编码转换工具
开发语言·c#
Jasper_o16 小时前
MassTransit OutBox 不发送消息问题
后端·.net
NQBJT18 小时前
嵌入式从零开始(第十二篇):调试与工具链 —— 从 IDE 到逻辑分析仪
ide·stm32·单片机·嵌入式硬件·c#
cici1587418 小时前
C# 五子棋小游戏源码(人机对战)
开发语言·单片机·c#