AutoMapper同实体集合映射空引用异常及密码映射问题排查
问题描述
使用AutoMapper进行同类型(Conturi→Conturi)实体映射时遇到两个核心问题:
- 从数据库获取的实体中
AdreseConturi集合为null,执行_mapper.Map(updatedUser, currentUser)时触发NullReferenceException。尝试过两种映射配置:忽略所有成员的简单映射、构造函数映射(构造函数会将null集合转为HashSet<Adrese>),均未解决报错。给AdreseConturi添加setter后空引用问题消失,但密码更新失效——构造函数内已获取到新密码值,但最终实体的Parola属性未更新。
问题根源
1. NullReferenceException 根源
- 调用
Map(source, destination)这种原地更新重载时,AutoMapper不会调用目标对象的构造函数,而是直接尝试为目标对象的属性赋值。 - 原
Conturi类的AdreseConturi只有getter无setter,且从数据库获取的currentUser.AdreseConturi为null。AutoMapper默认会尝试对ICollection类型属性执行集合合并操作(调用Add方法),但null集合无法调用Add方法,直接触发空引用异常。 - 构造函数映射仅在创建新对象场景(如
Map<Conturi>(source))生效,原地更新时不会执行构造逻辑。
2. 密码更新失效根源
- 定义了两套同类型的AutoMapper映射规则,AutoMapper只会使用最后注册的规则,且原地更新场景下不会触发构造函数逻辑。
- 即使构造函数映射生效,原地更新时AutoMapper不会替换整个目标对象,而是逐个属性赋值;若未明确配置
Parola属性的映射规则,会默认保留原对象值。
解决方案
针对空引用异常
有两种可行方案:
方案1:添加私有setter并配置集合映射(推荐,保留封装性)
修改Conturi类的AdreseConturi属性:
public ICollection<Adrese>? AdreseConturi { get; private set; }
配置AutoMapper规则:
CreateMap<Conturi, Conturi>() .ForMember(dest => dest.AdreseConturi, opt => { // 将null集合转为空HashSet opt.MapFrom(src => src.AdreseConturi ?? new HashSet<Adrese>()); // 优化集合映射(合并而非替换,按需选择) opt.OptimizeCollections(); });
方案2:原地更新前手动初始化null集合
在映射前手动处理null集合:
if(currentUser.AdreseConturi == null) { currentUser.AdreseConturi = new HashSet<Adrese>(); } _mapper.Map(updatedUser, currentUser);
针对密码更新失效
保留一套有效的AutoMapper配置,明确指定Parola属性的映射规则:
// 覆盖原有两套配置,只保留这一套 CreateMap<Conturi, Conturi>() // 忽略所有不需要更新的成员 .ForAllMembers(opt => opt.Ignore()) // 仅更新Parola属性,从上下文Items中获取新密码 .ForMember(dest => dest.Parola, opt => opt.MapFrom((src, dest, _, context) => context.Items.TryGetValue("NewPassword", out var newPwd) ? (string)newPwd : dest.Parola)) // 处理AdreseConturi集合,避免空引用 .ForMember(dest => dest.AdreseConturi, opt => { opt.MapFrom(src => src.AdreseConturi ?? new HashSet<Adrese>()); opt.OptimizeCollections(); });
修改调用代码,直接原地更新(无需先创建updatedUser):
var currentUser = await extendedRepository.GetByTokenAsync(changePasswordDto.TokenProp); if (currentUser == null) { throw new DbUpdateException("User not found"); } // 若使用方案1,此初始化步骤可省略 if(currentUser.AdreseConturi == null) { currentUser.AdreseConturi = new HashSet<Adrese>(); } // 直接原地更新,传入新密码参数 _mapper.Map(currentUser, currentUser, opt => opt.Items["NewPassword"] = changePasswordDto.NewHashedPasswordProp);
原报错信息
AutoMapper.AutoMapperMappingException: Error mapping types.
Mapping types:
Conturi -> Conturi
E_Commerce_BackEnd.Models.UserRelatedModels.Conturi -> E_Commerce_BackEnd.Models.UserRelatedModels.Conturi
Type Map configuration:
Conturi -> Conturi
E_Commerce_BackEnd.Models.UserRelatedModels.Conturi -> E_Commerce_BackEnd.Models.UserRelatedModels.Conturi
Destination Member:
AdreseConturi
---> System.NullReferenceException: Object reference not set to an instance of an object.
内容的提问来源于stack exchange,提问作者Misu Stefan

