Asp.Net Core 3.1 Web Api更新操作误删记录问题排查
问题现象
在 Asp.Net Core 3.1 Web API 中实现部门更新接口时,调用接口后目标部门记录被删除,未执行预期的更新操作。
涉及的数据表结构如下:
Id Name Active CreatedById CreateDate UpdatedById UpdateDate CompanyId RefId 310 HSE 1 54566 12/2/2021 8
Postman发起请求的操作过程:
原接口实现代码:
[HttpPut] [Route("~/api/entity/UpdateDepartment")] public async Task<IActionResult> UpdateDepartment([FromBody] TblCompanyDepartmentsXlu department) { try { Type dept = typeof(TblCompanyDepartmentsXlu); int DepartmentId = await companyService.FindDepartment(department.Id, department.RefId); TblCompanyDepartmentsXlu Department = await context.Departments.FirstOrDefaultAsync(x => x.Id == DepartmentId); foreach(PropertyInfo prop in dept.GetProperties()) { var vally = department.GetType().GetProperty(prop.Name); if(vally !=null) { dept.GetProperty(prop.Name).SetValue(Department, department.GetType().GetProperty(prop.Name).GetValue(department)); } } int EmployeeCompanyId = employeeService.GetEmployeeCompanyId(HttpContext); Department.UpdateById = EmployeeCompanyId; Department.UpdateDate = DateTime.Now; await context.SaveChangesAsync(); return Ok("Department updated successfully!"); } catch (Exception ex) { return Problem(ex.Message); } }
已尝试的排查手段:
- 在
SaveChangesAsync()前添加/移除context.Departments.Update(Department)语句,问题复现 - 替换反射赋值写法为手动逐字段赋值,问题仍存在
问题根因
- 反射全量赋值破坏EF Core变更追踪
当前反射逻辑没有做属性过滤,会把传入参数的所有属性值(包括主键、审计字段、导航属性)全量覆盖到已被上下文跟踪的数据库实体上:- 如果Postman传入的JSON漏传
Id字段,反序列化后Id默认值为0,会直接把原查询出的Id=310的实体主键覆盖为0。EF Core检测到已跟踪实体主键被修改时,会自动将原主键对应的记录标记为Deleted状态,将修改后主键的实体标记为Added状态,执行SaveChangesAsync时就会删除原记录。 - 如果传入参数包含导航属性(比如关联的员工集合、所属公司对象),反序列化生成的对象中这些属性默认是null或空集合,全量赋值给已跟踪实体后,EF Core会判定原有关联关系全部被移除,如果关系配置了级联删除规则,也会触发主实体被标记为删除。
- 如果Postman传入的JSON漏传
- 查询逻辑冗余易出错
更新操作本应直接通过前端传入的主键查询目标记录,额外调用FindDepartment通过Id+RefId查询时,因为RefId是本次要补充的新值,原记录中RefId为空,很容易返回错误的DepartmentId(比如0),导致后续操作的目标实体错误。 - 非更新字段未做隔离
反射逻辑没有区分可修改业务字段和固定系统字段,CreatedById/CreateDate这类创建后不应修改的字段、软删除标记字段、并发令牌字段都会被传入值覆盖,极易触发EF Core异常状态。
修复方案
- 修正查询逻辑,直接通过主键查询目标实体,移除多余的
FindDepartment调用,提前做参数合法性校验 - 限制反射赋值范围,只允许更新指定的业务字段,跳过主键、审计字段、导航属性
- 移除多余的
Update()调用,对EF Core已经跟踪的实体,直接修改属性值即可自动追踪变更,不需要手动调用Update方法
修复后代码参考:
[HttpPut] [Route("~/api/entity/UpdateDepartment")] public async Task<IActionResult> UpdateDepartment([FromBody] TblCompanyDepartmentsXlu department) { try { // 校验传入主键合法性 if (department.Id <= 0) { return BadRequest("部门ID参数非法"); } // 直接通过主键查询目标实体,不要用其他字段二次匹配 TblCompanyDepartmentsXlu dbDepartment = await context.Departments.FirstOrDefaultAsync(x => x.Id == department.Id); if (dbDepartment == null) { return NotFound("指定部门不存在"); } // 显式定义允许前端更新的字段列表,禁止全属性反射 var allowUpdateProps = new List<string> { nameof(TblCompanyDepartmentsXlu.Name), nameof(TblCompanyDepartmentsXlu.Active), nameof(TblCompanyDepartmentsXlu.RefId), nameof(TblCompanyDepartmentsXlu.CompanyId) }; Type deptType = typeof(TblCompanyDepartmentsXlu); foreach (var propName in allowUpdateProps) { PropertyInfo prop = deptType.GetProperty(propName); if (prop == null) continue; var newValue = prop.GetValue(department); // 可按需新增空值判断逻辑,决定空值是否覆盖原有值 prop.SetValue(dbDepartment, newValue); } int employeeCompanyId = employeeService.GetEmployeeCompanyId(HttpContext); dbDepartment.UpdateById = employeeCompanyId; dbDepartment.UpdateDate = DateTime.Now; await context.SaveChangesAsync(); return Ok("Department updated successfully!"); } catch (Exception ex) { return Problem(ex.Message); } }
另外需要确认Postman请求Body中正确传入了目标部门的Id值(即310),不要漏传或错传。
内容的提问来源于stack exchange,提问作者Rani Radcliff
相关产品推荐
相关产品推荐

