EF Core实体更新疑问:直接赋值实体无效及批量更新方法
问题描述
Controller中的Put方法
[HttpPut("{id}")] public async Task<LookupTable> Put(int id, [FromBody] LookupTable lookuptable) { var item = await lookupRepository.UpdateLookup(id, lookuptable); return item; }
仓库层无效的UpdateLookup写法
public async Task<LookupTable> UpdateLookup(int id, LookupTable entity) { LookupTable item = await riskDBContext.LookupTables.Where(c => c.LookupId == id).FirstOrDefaultAsync(); item = entity; var result = await riskDBContext.SaveChangesAsync(); return item; }
仓库层有效的UpdateLookup写法
public async Task<LookupTable> UpdateLookup(int id, LookupTable entity) { LookupTable item = await riskDBContext.LookupTables.Where(c => c.LookupId == id).FirstOrDefaultAsync(); item.LookupAttribute = entity.LookupAttribute; var result = await riskDBContext.SaveChangesAsync(); return item; }
疑问
- 为何第一种写法无法更新数据库,第二种可以?
- 由于LookupTable属性较多,不想逐个赋值,有没有一次性更新所有属性的方法?
解答
原因分析
这是Entity Framework Core(EF Core)的实体跟踪机制导致的:
- 从数据库查询出来的
item对象,是被riskDBContext上下文跟踪的,EF会持续监控这个对象的属性变化。 - 执行
item = entity时,只是把局部变量item的引用指向了传入的entity对象(这个对象来自请求体序列化,未被当前上下文跟踪)。原来被跟踪的数据库实体对象根本没有被修改,所以SaveChangesAsync()执行时,EF检测不到任何变更,不会生成数据库更新语句。 - 第二种写法直接修改了被跟踪对象
item的属性值,EF能捕获到这些属性变化,执行SaveChangesAsync()时就会生成对应的UPDATE语句同步到数据库。
无需逐个赋值的批量更新方法
方法1:使用EF Core原生的Entry.SetValues
这是EF官方推荐的批量赋值方式,直接将传入实体的属性覆盖到被跟踪的实体上:
public async Task<LookupTable> UpdateLookup(int id, LookupTable entity) { var item = await riskDBContext.LookupTables.Where(c => c.LookupId == id).FirstOrDefaultAsync(); riskDBContext.Entry(item).CurrentValues.SetValues(entity); await riskDBContext.SaveChangesAsync(); return item; }
该方法会自动匹配同名属性进行赋值,同时保留EF的变更跟踪能力。
方法2:使用EF Core的Update方法
直接将传入的实体附加到上下文并标记为修改状态,EF会生成更新所有属性的语句:
public async Task<LookupTable> UpdateLookup(int id, LookupTable entity) { // 确保实体主键与路由ID一致 entity.LookupId = id; riskDBContext.LookupTables.Update(entity); await riskDBContext.SaveChangesAsync(); return entity; }
注意:如果传入的entity中有未赋值的属性(比如请求体未传递的字段),会被设置为默认值覆盖数据库中的原有值,需谨慎使用。
方法3:使用AutoMapper批量映射属性
通过AutoMapper工具自动完成属性复制,可灵活配置映射规则:
// 先在项目中配置AutoMapper映射规则(LookupTable到LookupTable) public async Task<LookupTable> UpdateLookup(int id, LookupTable entity) { var item = await riskDBContext.LookupTables.Where(c => c.LookupId == id).FirstOrDefaultAsync(); _mapper.Map(entity, item); // 将entity的属性复制到被跟踪的item对象 await riskDBContext.SaveChangesAsync(); return item; }
这种方式可以轻松排除不需要更新的字段(比如主键、创建时间等),避免意外覆盖。
方法4:通过反射手动批量赋值
如果不想引入第三方库,可通过反射遍历属性完成赋值:
public async Task<LookupTable> UpdateLookup(int id, LookupTable entity) { var item = await riskDBContext.LookupTables.Where(c => c.LookupId == id).FirstOrDefaultAsync(); // 获取可写入的属性,排除主键 var properties = typeof(LookupTable).GetProperties() .Where(p => p.CanWrite && p.Name != nameof(LookupTable.LookupId)); foreach (var prop in properties) { var value = prop.GetValue(entity); prop.SetValue(item, value); } await riskDBContext.SaveChangesAsync(); return item; }
注意:需要提前明确排除不需要更新的属性,避免出现逻辑错误。
内容的提问来源于stack exchange,提问作者Venkat
相关产品推荐
相关产品推荐

