.NET Core中EmployeeInfo表增改逻辑异常问题求助
解决方案:EF Core实现EmployeeInfo的Upsert(存在更新,不存在插入)
问题根源
- 直接设置实体状态报错:你传入的
employee是表单提交的脱离上下文对象,其EmployeeInfoId大概率为0。将状态设为Modified时,EF会尝试更新主键为0的记录,但数据库中无对应行,因此触发并发异常。 - 直接调用
Update仅插入不更新:同样因为传入实体的主键为0,EF默认将其判定为新实体,执行插入而非更新操作。
正确实现方式
方法一:查询+属性映射(兼容所有EF Core版本)
先按EmployeeNumber和PolicyYear(业务唯一键)查询数据库,存在则更新属性,不存在则新增。这种方式能正确处理导航属性,避免主键不匹配问题:
public async Task UpdateEmployee(EmployeeInfo employee) { // 查询数据库中是否存在目标记录 var existingEmployee = await _ackContext.EmployeeInfos .Include(e => e.JobTitleLookup) .Include(e => e.LocationLookup) .FirstOrDefaultAsync(x => x.EmployeeNumber == employee.EmployeeNumber && x.PolicyYear == employee.PolicyYear); if (existingEmployee != null) { // 复制提交的属性值到数据库已加载的实体 _ackContext.Entry(existingEmployee).CurrentValues.SetValues(employee); // 处理JobTitleLookup导航属性 if (employee.JobTitleLookup != null) { if (existingEmployee.JobTitleLookup == null) { _ackContext.JobTitleLookups.Attach(employee.JobTitleLookup); existingEmployee.JobTitleLookup = employee.JobTitleLookup; } else { _ackContext.Entry(existingEmployee.JobTitleLookup).CurrentValues.SetValues(employee.JobTitleLookup); } } // 处理LocationLookup导航属性 if (employee.LocationLookup != null) { if (existingEmployee.LocationLookup == null) { _ackContext.LocationLookups.Attach(employee.LocationLookup); existingEmployee.LocationLookup = employee.LocationLookup; } else { _ackContext.Entry(existingEmployee.LocationLookup).CurrentValues.SetValues(employee.LocationLookup); } } } else { // 附加已有Lookup实体,避免重复插入 if (employee.JobTitleLookup != null && employee.JobTitleLookup.JobTitleLookupId > 0) { _ackContext.JobTitleLookups.Attach(employee.JobTitleLookup); } if (employee.LocationLookup != null && employee.LocationLookup.LocationLookupId > 0) { _ackContext.LocationLookups.Attach(employee.LocationLookup); } // 新增实体 _ackContext.EmployeeInfos.Add(employee); } await _ackContext.SaveChangesAsync(); }
方法二:EF Core 7+原生批量更新(性能优化)
如果你使用EF Core 7及以上版本,可以用ExecuteUpdate实现无实体加载的高效更新:
public async Task UpdateEmployee(EmployeeInfo employee) { // 尝试更新现有记录 var updatedCount = await _ackContext.EmployeeInfos .Where(x => x.EmployeeNumber == employee.EmployeeNumber && x.PolicyYear == employee.PolicyYear) .ExecuteUpdateAsync(setters => setters .SetProperty(e => e.LastName, employee.LastName) .SetProperty(e => e.FirstName, employee.FirstName) .SetProperty(e => e.DateFormFilled, employee.DateFormFilled) // 按需添加其他需要更新的属性 ); if (updatedCount == 0) { // 无匹配记录,执行插入 if (employee.JobTitleLookup != null && employee.JobTitleLookup.JobTitleLookupId > 0) { _ackContext.JobTitleLookups.Attach(employee.JobTitleLookup); } if (employee.LocationLookup != null && employee.LocationLookup.LocationLookupId > 0) { _ackContext.LocationLookups.Attach(employee.LocationLookup); } _ackContext.EmployeeInfos.Add(employee); await _ackContext.SaveChangesAsync(); } }
关键注意事项
- 唯一约束:确保数据库中
EmployeeNumber和PolicyYear的组合设置了唯一约束,避免重复记录。 - 导航属性处理:如果
JobTitleLookup和LocationLookup是数据库已有数据,必须先调用Attach将其加入上下文,否则EF会尝试插入新的Lookup记录。 - 属性复制优化:如果实体属性较多,可使用AutoMapper的
Mapper.Map(employee, existingEmployee)代替手动属性复制,简化代码。
内容的提问来源于stack exchange,提问作者rimi
相关产品推荐
相关产品推荐

