使用EF Core更新对象:非空属性才更新的替代实现方案咨询
灵活更新实体属性的实现方案探讨
我正在开发UpdateEntityCommand,需要实现这样的更新逻辑:当UpdateDrinkDto中的属性为null时,不修改对应Drink实体的属性值,仅更新非空的属性。目前我通过反射实现了这个功能,但不确定这种方式是否合理,想咨询有没有其他更优的实现方式。
相关代码
Drink实体
public class Drink : Product { public string Name { get; set; } public int Id { get; set; } public double Price { get; set; } public string? Explanation { get; set; } public double? Calories { get; set; } public bool HasStock { get; set; } = true; public int? Volume { get; set; } public int DrinkCategoryId { get; set; } public virtual DrinkCategory DrinkCategory { get; set; } }
UpdateDrinkDto
public class UpdateDrinkDto { public int Id { get; set; } public string? Name { get; set; } public double? Price { get; set; } public bool? HasStock { get; set; } = true; public string? Explanation { get; set; } public int? Volume { get; set; } }
当前反射实现的Handle方法
public async Task<DrinkDto> Handle(UpdateDrinkCommand request, CancellationToken cancellationToken) { Drink? drink = await _drinkRepository.GetAsync(x => x.Id == request.UpdateDrinkDto.Id, x => x.Include(x=>x.DrinkCategory).ThenInclude(x=>x.Menu)); _drinkBusinessRules.DoesDrinkExists(drink); await _businessRules.IsOwnerResponsibleForRestaurant(request.OwnerId, drink.DrinkCategory.Menu.RestaurantId); SetProperties(request.UpdateDrinkDto, drink); Drink updatedDrink = await _drinkRepository.UpdateAsync(drink); DrinkDto response = _mapper.Map<DrinkDto>(updatedDrink); return response; } private void SetProperties(UpdateDrinkDto updateDrinkDto, Drink drink) { PropertyInfo[] properties = typeof(UpdateDrinkDto).GetProperties(); foreach (PropertyInfo property in properties) { var value = updateDrinkDto.GetType().GetProperty(property.Name).GetValue(updateDrinkDto); if (value != null) { typeof(Drink).GetProperty(property.Name)?.SetValue(drink, value); } } }
反射实现的优缺点
优点
- 灵活性高:新增属性时无需修改映射逻辑,自动适配
- 代码简洁:不用逐个编写属性判断逻辑
缺点
- 性能损耗:反射操作比直接调用属性慢,高频场景下影响明显
- 类型不安全:编译时无法检查属性名是否匹配、类型是否兼容,容易出现运行时错误
- 调试困难:出现问题时难以定位具体是哪个属性映射出错
替代实现方案
1. 手动属性映射(类型安全,性能最优)
直接逐个判断DTO的属性值,非空则更新实体。虽然代码量多,但完全可控,编译时就能发现错误。
private void UpdateDrinkProperties(UpdateDrinkDto dto, Drink drink) { if (dto.Name != null) drink.Name = dto.Name; if (dto.Price != null) drink.Price = dto.Price.Value; if (dto.HasStock != null) drink.HasStock = dto.HasStock.Value; if (dto.Explanation != null) drink.Explanation = dto.Explanation; if (dto.Volume != null) drink.Volume = dto.Volume.Value; }
2. AutoMapper条件映射
配置AutoMapper时,添加条件规则,仅当源属性非空时才进行映射。
首先在AutoMapper配置文件中添加:
CreateMap<UpdateDrinkDto, Drink>() .ForAllMembers(opt => opt.Condition((src, dest, srcVal) => srcVal != null));
然后在Handle方法中直接使用Mapper:
// 替换原来的SetProperties调用 _mapper.Map(request.UpdateDrinkDto, drink);
3. 表达式树封装通用方法
表达式树比反射性能好,且能保持类型安全,可以封装成通用工具类,复用不同实体和DTO的更新逻辑。
示例通用方法:
public static void UpdateNonNullProperties<TSource, TTarget>(TSource source, TTarget target) where TSource : class where TTarget : class { var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead); var targetProperties = typeof(TTarget).GetProperties().Where(p => p.CanWrite); foreach (var sourceProp in sourceProperties) { var targetProp = targetProperties.FirstOrDefault(p => p.Name == sourceProp.Name && p.PropertyType == sourceProp.PropertyType); if (targetProp == null) continue; var value = sourceProp.GetValue(source); if (value != null) { targetProp.SetValue(target, value); } } }
调用时直接用:
UpdateNonNullProperties(request.UpdateDrinkDto, drink);
4. 使用Mapster库
Mapster是一个轻量的对象映射库,支持条件映射,配置简单且性能接近手动映射。
安装Mapster后,配置映射规则:
TypeAdapterConfig<UpdateDrinkDto, Drink>.NewConfig() .IgnoreNullValues(true);
然后在Handle方法中使用:
request.UpdateDrinkDto.Adapt(drink);
内容的提问来源于stack exchange,提问作者ashwor11
相关产品推荐
相关产品推荐

