如何通过Automapper优雅实现用户更新时Attributes的增量更新?
问题描述
我正在实现用户更新接口,更新时会先获取原始UserEntity实体:
public class UserEntity { public Guid Id { get; set; } public Dictionary<string, IEnumerable<string>> Attributes { get; set; } }
var user = await _userService.GetByIdAsync(request.Id);
更新请求定义如下:
public class UpdateUserRequest { public string? MiddleName { get; set; } public string? Phone { get; set; } }
其中MiddleName和Phone需要存入UserEntity的Attributes字典中。我当前使用Automapper映射时,通过UseDestinationValue()保留原有Attributes避免被清空:
_mapper.Map(request, user); CreateMap<UpdateUserRequest, UserEntity>().ForMember(dst => dst.Attributes, opt => opt.UseDestinationValue());
但目前我通过自定义SetAttributes方法来实现新增或更新Attributes中的字段,写法较为繁琐,想请教是否可以通过Automapper优雅处理该需求?
我的现有实现:
void SetAttributes() { if(!string.IsNullOrEmpty(request.MiddleName)) { if(!user.Attributes.TryGetValue(Constants.Attributes.MiddleName, out var middleName)) { user.Attributes.Add(Constants.Attributes.MiddleName, new string[] { request.MiddleName }); } else { user.Attributes[Constants.Attributes.MiddleName] = new string[] { request.MiddleName }; } } if(!string.IsNullOrEmpty(request.Phone)) { if(!user.Attributes.TryGetValue(Constants.Attributes.PhoneNumber, out var firstName)) { user.Attributes.Add(Constants.Attributes.PhoneNumber, new string[] { request.Phone }); } else { user.Attributes[Constants.Attributes.PhoneNumber] = new string[] { request.Phone }; } } }
解决方案
可以通过Automapper的字段映射配置结合自定义逻辑,替代繁琐的SetAttributes方法,以下是两种优雅实现方式:
方式1:逐个字段配置映射逻辑
直接在CreateMap中为每个请求字段添加自定义映射规则,判断字段非空后更新字典:
CreateMap<UpdateUserRequest, UserEntity>() // 保留原有Attributes字典实例 .ForMember(dst => dst.Attributes, opt => opt.UseDestinationValue()) // 处理MiddleName字段 .ForMember(dst => dst.Attributes, opt => opt.MapFrom((src, dst) => { if (!string.IsNullOrEmpty(src.MiddleName)) { dst.Attributes[Constants.Attributes.MiddleName] = new[] { src.MiddleName }; } return dst.Attributes; })) // 处理Phone字段 .ForMember(dst => dst.Attributes, opt => opt.MapFrom((src, dst) => { if (!string.IsNullOrEmpty(src.Phone)) { dst.Attributes[Constants.Attributes.PhoneNumber] = new[] { src.Phone }; } return dst.Attributes; }));
调用_mapper.Map(request, user)时,会自动完成对应字典项的新增或更新,无需额外调用自定义方法。
方式2:自定义值解析器(复用逻辑)
如果后续需要处理更多字段,可封装通用值解析器减少重复代码:
首先定义通用解析器:
public class UpdateAttributesResolver<TSource> : IValueResolver<TSource, UserEntity, Dictionary<string, IEnumerable<string>>> { private readonly Func<TSource, (string Key, string? Value)> _propertySelector; public UpdateAttributesResolver(Func<TSource, (string Key, string? Value)> propertySelector) { _propertySelector = propertySelector; } public Dictionary<string, IEnumerable<string>> Resolve(TSource source, UserEntity destination, Dictionary<string, IEnumerable<string>> destMember, ResolutionContext context) { var (key, value) = _propertySelector(source); if (!string.IsNullOrEmpty(value)) { destination.Attributes[key] = new[] { value }; } return destination.Attributes; } }
然后在映射配置中使用:
CreateMap<UpdateUserRequest, UserEntity>() .ForMember(dst => dst.Attributes, opt => opt.UseDestinationValue()) .ForMember(dst => dst.Attributes, opt => opt.AddResolver(new UpdateAttributesResolver<UpdateUserRequest>(src => (Constants.Attributes.MiddleName, src.MiddleName)))) .ForMember(dst => dst.Attributes, opt => opt.AddResolver(new UpdateAttributesResolver<UpdateUserRequest>(src => (Constants.Attributes.PhoneNumber, src.Phone))));
新增字段时只需添加一行AddResolver即可,扩展性更强。
注意事项
- 确保
UserEntity的Attributes字典在映射前已初始化(比如从数据库获取时不为null),避免空引用异常。 - 若需要处理字段为null时删除字典项的场景,可在解析器中补充逻辑:当
value为null且字典存在对应key时,执行destination.Attributes.Remove(key)。
内容的提问来源于stack exchange,提问作者hoozr
相关产品推荐
相关产品推荐

