AutoMapper 配置必须在应用启动时一次性完成,通过 MapperConfiguration 全局注册映射规则,禁止运行时重复创建;推荐使用 Profile 拆分维护,配合 AddAutoMapper 自动扫描,并调用 AssertConfigurationIsValid 验证有效性。

AutoMapper 配置必须在应用启动时完成
映射配置不是每次调用 Map() 时临时生效的,而是一次性注册、全局复用。如果在方法里反复调用 CreateMap<TSource, TDestination>(),不仅性能差,还会触发 AutoMapperConfigurationException —— 因为 MapperConfiguration 实例是只读的,构建后不可修改。
正确做法是在 Program.cs(.NET 6+)或 Global.asax(旧版)中一次性初始化:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, UserDto>();
cfg.CreateMap<Order, OrderSummaryDto>()
.ForMember(dest => dest.Total, opt => opt.MapFrom(src => src.Items.Sum(i => i.Price)));
});
关键点:
- 配置对象
MapperConfiguration应作为单例注入(如用services.AddAutoMapper()),避免手动 new 多次 - 不要在 Controller 或 Service 方法里写
new MapperConfiguration(...),那是典型内存泄漏+重复配置 - 若需动态类型映射(比如运行时才知道源/目标类型),改用
IMapper.DynamicMap<T>(),但注意它不走预编译表达式,性能略低
ForMember 里别直接写 null 判断逻辑
常见错误是把空值处理硬编码进 ForMember 的 MapFrom 委托里,比如:
.ForMember(d => d.Name, o => o.MapFrom(s => s.FullName ?? "N/A"))
这看起来没问题,但会绕过 AutoMapper 的表达式树优化,导致该属性映射无法被编译成高效委托,尤其在高频调用场景(如 Web API 返回列表)下,GC 压力明显上升。
更稳妥的方式是用 NullSubstitute 或前置转换:
- 用
NullSubstitute:.ForMember(d => d.Name, o => o.NullSubstitute("N/A"))—— 简洁且保留编译优化 - 需要复杂逻辑?抽成自定义 resolver:
.ResolveUsing<FullNameResolver>(),并在类里实现IValueResolver<User, UserDto, string> - 绝对不要在
MapFrom里调用数据库查询、HTTP 请求等副作用操作 —— 映射器不是执行引擎
Profile 类型是组织映射逻辑的实际单位
当项目映射关系超过 5 对,硬塞进一个 MapperConfiguration 匿名委托里就难维护了。此时 Profile 不是可选项,而是必需项 —— 它让映射配置可拆分、可测试、可按模块加载。
例如按业务域建 Profile:
public class UserMappingProfile : Profile
{
public UserMappingProfile()
{
CreateMap<User, UserDto>();
CreateMap<UserDto, User>().ForMember(u => u.PasswordHash, opt => opt.Ignore());
}
}
然后注册时自动扫描:
services.AddAutoMapper(cfg => cfg.AddProfile<UserMappingProfile>(), typeof(Program).Assembly);
注意坑点:
- 多个
Profile中对同一对类型重复调用CreateMap,不会报错但后者覆盖前者 —— 检查是否误引入了两个同名 Profile -
AddProfile<T>()和AddProfiles(assembly)行为不同:前者只加一个,后者扫描所有继承Profile的类,容易漏配或重配 - Profile 构造函数里不能依赖 DI 容器中的服务(如
IHttpClientFactory),因为 Profile 实例化早于服务注册完成
忽略属性时 Ignore() 和 ForPath() 的适用边界
想跳过某个属性映射,90% 场景用 Ignore() 就够了;但遇到嵌套对象里的深层字段(比如 User.Address.Street),Ignore() 会失效 —— 它只作用于当前层级。
这时候得用 ForPath():
CreateMap<User, UserDto>()
.ForPath(d => d.Address.Street, opt => opt.Ignore());
另外要注意:
-
Ignore()是“完全跳过”,不设默认值;若希望设默认值(如 0、false、string.Empty),用DefaultValue()或UseValue() - 用
ForPath()忽略深层字段时,路径必须严格匹配属性名大小写和嵌套结构,拼错一个字母就静默失败(无异常,也不映射) - 如果目标类型字段是只读属性(只有 getter),AutoMapper 默认不映射;需显式启用:
cfg.AllowNullCollections = true;或用ConstructUsing
最常被忽略的是:配置完 Profile 后没验证映射是否真能跑通。上线前至少跑一次 config.AssertConfigurationIsValid();,它能提前暴露类型不匹配、未映射必填字段等问题。


















