AutoMapper依赖注入缓存导致映射含共享类的不同类时出现500错误
AutoMapper + DI 下共享自定义集合类型导致的映射空引用异常分析与解决
问题场景
我们有一个使用AutoMapper和依赖注入的C# Web服务,控制器包含A、B两个接口,各自有独立的DTO与资源类,调用时都会执行DTO到资源类的映射。两个DTO均包含**AutoMappingSharedCollection
IIS重置后,按「先调用A再调用B」的顺序操作时,B接口会触发嵌套的AutoMapper异常,最终根源是NullReferenceException。临时解决办法有两个:
- 在控制器内本地创建Mapper实例(而非使用DI注入的实例),不会出错
- 将B接口DTO中的自定义集合替换为普通
List<T>,也能规避错误
原因分析
核心问题出在AutoMapper的映射缓存机制与DI单例生命周期的冲突:
- AutoMapper首次处理映射时,会自动生成并缓存类型映射计划,对于自定义集合类型,会生成对应的集合转换逻辑
- 首次调用A接口时,AutoMapper为
AutoMappingSharedCollection<AutoMappingSharedClass>生成了到List<AutoMappingSharedClassMapped>的映射逻辑并缓存 - 由于DI注入的Mapper默认是单例,这个缓存会被所有后续请求共享
- 当调用B接口时,B的目标集合是
AutoMappingSharedCollection<AutoMappingSharedClassMapped>,但AutoMapper错误复用了之前缓存的「到List」的映射逻辑,无法正确初始化B的自定义集合实例,最终抛出空引用异常
注:如果先调用B,AutoMapper会先缓存自定义集合到自定义集合的映射逻辑,后续调用A时也能正常适配,因此不会出错。
规避方案
方案1:显式配置自定义集合的映射规则
在AutoMapper Profile中,为自定义集合的不同目标类型显式编写映射逻辑,避免AutoMapper自动推断错误的缓存:
public AutoMappingWeb() { // 保留原有实体类映射配置 CreateMap<AutoMappingClassALarge, AutoMappingClassALargeMapped>() .ForMember(dest => dest.ClassAMappedList, opt => opt.MapFrom(src => src.ClassAList)); CreateMap<AutoMappingClassA, AutoMappingClassAMapped>() .ForMember(dest => dest.Data, opt => opt.MapFrom(src => src.Data)); CreateMap<AutoMappingSharedClass, AutoMappingSharedClassMapped>() .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)); CreateMap<AutoMappingClassBLarge, AutoMappingClassBLargeMapped>(); CreateMap<AutoMappingClassB, AutoMappingClassBMapped>(); // 显式配置自定义集合到List的映射 CreateMap<AutoMappingSharedCollection<AutoMappingSharedClass>, List<AutoMappingSharedClassMapped>>() .ConvertUsing((src, _, context) => src.Select(item => context.Mapper.Map<AutoMappingSharedClassMapped>(item)).ToList()); // 显式配置自定义集合到自定义集合的映射 CreateMap<AutoMappingSharedCollection<AutoMappingSharedClass>, AutoMappingSharedCollection<AutoMappingSharedClassMapped>>() .ConvertUsing((src, _, context) => { var result = new AutoMappingSharedCollection<AutoMappingSharedClassMapped>(); foreach (var item in src) { result.Add(context.Mapper.Map<AutoMappingSharedClassMapped>(item)); } return result; }); }
方案2:调整Mapper的DI生命周期
将注入的Mapper从单例改为**范围(Scoped)**生命周期,让每个请求都使用独立的Mapper实例,避免跨请求的缓存冲突:
在Program.cs(或Startup.cs)中修改DI配置:
// 替换原有的AddAutoMapper注册方式 services.AddAutoMapper(typeof(AutoMappingWeb)); // 显式注册Scoped的IMapper services.AddScoped<IMapper>(sp => { var config = sp.GetRequiredService<MapperConfiguration>(); return config.CreateMapper(); });
方案3:统一目标集合类型
如果业务允许,将A、B接口的目标集合类型统一(比如都使用自定义集合或都使用List<T>),消除AutoMapper缓存混淆的前提。
内容的提问来源于stack exchange,提问作者CGit
相关产品推荐
相关产品推荐

