如何实现同类型类的属性自动映射?(ASP.NET Core代码问题)
问题场景
要把传入的TempProducts类型对象product的所有属性,映射到从数据库查询出来的同类型实体result上。手动逐个赋值属性时,EF Core的ChangeTracker能正常检测到变更并保存,但直接给result赋值product、或者错误使用AutoMapper的方式都无效,ChangeTracker追踪不到变更。
错误方式的原因
1. 直接赋值result = product无效
从数据库查出来的result是被EF ChangeTracker跟踪的对象引用,执行result = product只是把变量result的指向换成了传入的product(这个对象不在EF的跟踪列表里),原来被跟踪的实体对象根本没被修改,所以EF检测不到任何变更。
2. AutoMapper错误用法无效
代码里用result = mapper.Map<TempProducts, TempProducts>(product),本质是创建了一个新的TempProducts对象(或者直接替换了引用),同样是把result的引用从原来被EF跟踪的对象换成了新对象,原跟踪实体没被改动,自然也触发不了变更追踪。
正确解决方法
方法一:AutoMapper映射到已有跟踪对象
用AutoMapper的Map(source, destination)重载,把源对象的属性值复制到已经被EF跟踪的目标对象上,这样就能修改原跟踪实体的属性,触发变更检测。
[HttpPost] [ValidateAntiForgeryToken] public IActionResult Edit(TempProducts product) { if (ModelState.IsValid) { // 建议把AutoMapper配置放到全局(如Program.cs),不要每次请求都创建 var config = new MapperConfiguration(c => { c.CreateMap<TempProducts, TempProducts>() .ForMember(dest => dest.ID, opt => opt.Ignore()); // 主键不需要被覆盖,忽略映射 }); IMapper mapper = config.CreateMapper(); var result = _db.Product.FirstOrDefault(s => s.ID == product.ID); if (result != null) { // 关键:映射到已有的result对象,而非替换引用 mapper.Map(product, result); _db.SaveChanges(); return RedirectToAction(nameof(Pass2.index)); } } return View(product); }
方法二:使用EF Core的Update方法(简单场景)
如果不需要精细控制哪些字段被修改,可以直接把传入的product附加到上下文并调用Update,EF会标记该对象的所有属性为已修改:
public IActionResult Edit(TempProducts product) { if (ModelState.IsValid) { _db.Product.Update(product); _db.SaveChanges(); return RedirectToAction(nameof(Pass2.index)); } return View(product); }
注意:这种方式会生成更新所有字段的SQL,即使部分属性没有变化。如果需要只更新修改过的字段,优先选AutoMapper映射到跟踪对象的方式。
方法三:反射批量赋值(替代手动逐个赋值)
不想用AutoMapper的话,可以用反射批量复制属性,省去手动写一堆赋值语句的麻烦:
public IActionResult Edit(TempProducts product) { if (ModelState.IsValid) { var result = _db.Product.FirstOrDefault(s => s.ID == product.ID); if (result != null) { // 获取所有可写且非主键的属性 var properties = typeof(TempProducts).GetProperties() .Where(p => p.Name != nameof(TempProducts.ID) && p.CanWrite); foreach (var prop in properties) { var value = prop.GetValue(product); prop.SetValue(result, value); } _db.SaveChanges(); return RedirectToAction(nameof(Pass2.index)); } } return View(product); }
核心要点
不管用哪种方式,核心都是修改EF ChangeTracker已经跟踪的实体对象的属性值,而不是替换实体对象的引用——只有修改跟踪对象的属性,EF才能检测到变更并生成对应的更新SQL。
内容的提问来源于stack exchange,提问作者Memba

