使用EF Core与Visual Studio的Razor Pages应用并发冲突未被识别
并发冲突检测失效问题修复
问题描述
按照ASP.NET Core EF RP并发实现指南调整代码后,并发冲突无法被检测:打开两个标签页编辑同一条Department记录,先保存第一个标签页的修改,再在未刷新的第二个标签页保存修改,系统无任何并发冲突提示,直接覆盖数据库记录,所有并发检查代码被跳过。手动修改代码中的ConcurrencyToken也无法触发冲突检测。
核心原因
- 手动覆盖并发令牌:在
OnPostAsync中手动设置departmentToUpdate.ConcurrencyToken = Guid.NewGuid().ToByteArray();,直接替换了从数据库加载的原始令牌,导致EF Core无法用客户端提交的旧令牌与数据库当前令牌做对比。 - 未关联客户端提交的令牌:加载数据库实体后,没有将表单提交的
ConcurrencyToken赋值给要更新的实体,EF Core无法识别这是一个基于旧版本的更新操作。
修复步骤
1. 移除手动设置令牌的代码
删除OnPostAsync中这行代码:
departmentToUpdate.ConcurrencyToken = Guid.NewGuid().ToByteArray();
2. 关联客户端提交的并发令牌
在更新实体属性后,添加代码将表单提交的令牌赋值给加载的数据库实体:
// 手动更新属性 departmentToUpdate.Name = Department.Name; departmentToUpdate.Budget = Department.Budget; departmentToUpdate.StartDate = Department.StartDate; departmentToUpdate.InstructorID = Department.InstructorID; // 新增:将客户端提交的旧并发令牌赋值给要更新的实体 departmentToUpdate.ConcurrencyToken = Department.ConcurrencyToken;
3. (可选)显式配置并发令牌(增强稳定性)
在SchoolContext的OnModelCreating方法中,显式配置ConcurrencyToken为行版本:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Department>() .Property(d => d.ConcurrencyToken) .IsRowVersion(); // 标记为数据库自动维护的行版本 }
修改后的OnPostAsync关键代码片段
public async Task<IActionResult> OnPostAsync(int id) { _logger.LogInformation("Starting to handle POST request for department with ID: {ID}", id); ModelState.Remove("Department.Administrator"); var departmentToUpdate = await _context.Departments .Include(i => i.Administrator) .FirstOrDefaultAsync(m => m.DepartmentID == id); if (departmentToUpdate == null) { return HandleDeletedDepartment(id); } // 手动更新属性 departmentToUpdate.Name = Department.Name; departmentToUpdate.Budget = Department.Budget; departmentToUpdate.StartDate = Department.StartDate; departmentToUpdate.InstructorID = Department.InstructorID; // 关联客户端提交的旧并发令牌 departmentToUpdate.ConcurrencyToken = Department.ConcurrencyToken; // 处理Administrator关联逻辑... if (Department.InstructorID.HasValue) { departmentToUpdate.Administrator = await _context.Instructors .FindAsync(Department.InstructorID.Value); } else { departmentToUpdate.Administrator = null; } if (!ModelState.IsValid) { InstructorNameSL = new SelectList(_context.Instructors, "ID", "FullName", departmentToUpdate.InstructorID); return Page(); } try { await _context.SaveChangesAsync(); return RedirectToPage("./Index"); } catch (DbUpdateConcurrencyException ex) { // 原有并发冲突处理逻辑... _logger.LogError(ex, "Concurrency conflict for department with ID: {ID}", departmentToUpdate.DepartmentID); var exceptionEntry = ex.Entries.Single(); var databaseEntry = await exceptionEntry.GetDatabaseValuesAsync(); if (databaseEntry == null) { ModelState.AddModelError(string.Empty, "Unable to save changes. The department was deleted by another user."); } else { var dbValues = (Department)databaseEntry.ToObject(); ModelState.AddModelError(string.Empty, "The record you attempted to edit was modified by another user. If you still want to edit this record, click Save again. Otherwise, click Cancel."); Department.ConcurrencyToken = (byte[])dbValues.ConcurrencyToken; } InstructorNameSL = new SelectList(_context.Instructors, "ID", "FullName", departmentToUpdate.InstructorID); return Page(); } InstructorNameSL = new SelectList(_context.Instructors, "ID", "FullName", departmentToUpdate.InstructorID); return Page(); }
验证方式
- 打开两个标签页进入同一条Department的编辑页面
- 在第一个标签页修改属性并保存
- 返回第二个未刷新的标签页,修改属性并保存
- 此时系统会触发
DbUpdateConcurrencyException,进入异常处理分支,显示并发冲突提示信息
内容的提问来源于stack exchange,提问作者user24313025
相关产品推荐
相关产品推荐

