ASP.NET MVC使用EF Core时多次调用控制器方法导致购物车更新异常如何解决
问题诱因
核心原因是快速多次点击触发了并发请求的竞态条件,结合你当前的代码逻辑,具体表现为:
- ASP.NET Core中每个请求对应独立的EF Core DbContext实例,多个并行请求之间的上下文数据互不感知
- 两次请求几乎同时查询到相同初始值的
CartItem,各自对Quantity做+1计算后,后完成的请求的SaveChanges会直接覆盖先完成请求的修改,导致cartItemPrice看起来只更新了一次 CartTotalPrice始终能更新是因为两次请求的AddToTotalPrice方法会分别读取当前最新的总价做累加,不会出现覆盖的情况
可行解决方案
1. 前端先做防重复点击(成本最低,见效最快)
给+号按钮增加点击后立即禁用的逻辑,从入口减少重复请求的产生,示例JS代码:
// 页面加载完成后绑定事件 document.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('.add.quantity-btn').forEach(btn => { btn.addEventListener('click', function() { // 禁用点击,防止重复触发 this.style.pointerEvents = 'none'; this.classList.add('disabled'); // 页面刷新后会自动恢复可点击状态,如果是AJAX请求则在请求回调中恢复 }); }); });
2. 后端优化事务逻辑,减少不必要的多次提交
你当前代码中AddToTotalPrice单独调用一次SaveChanges,AddOne最后又调用一次SaveChanges,一个请求两次数据提交,增大了并发冲突的概率,建议将两个修改合并到同一个事务中一次提交:
public async Task AddOne(int id) { // 开启事务,保证所有修改要么全部成功要么全部失败 using var transaction = await _context.Database.BeginTransactionAsync(); try { var cartItem = await _context.CartItem.Include(d => d.Dish).FirstOrDefaultAsync(s => s.Id == id); var currentUser = GetCurrentUserName(); var cart = await _context.Cart.FirstOrDefaultAsync(p => p.UserName == currentUser); if (cartItem == null || cartItem.CartId != cart.Id) { await transaction.RollbackAsync(); return; } cartItem.Quantity += 1; cartItem.cartItemPrice = cartItem.Dish.Price * cartItem.Quantity; cart.CartTotalPrice += cartItem.Dish.Price; // 一次保存所有修改,不需要单独的AddToTotalPrice方法 await _context.SaveChangesAsync(); await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw; } }
3. 增加乐观锁避免修改覆盖
给CartItem实体增加行版本字段作为并发校验标记,EF Core检测到并发冲突时会自动抛出异常,避免修改覆盖,示例配置:
// CartItem实体类增加行版本字段 public class CartItem { // 其他原有字段 [Timestamp] public byte[] RowVersion { get; set; } }
配置后如果出现并发修改,EF Core会抛出DbUpdateConcurrencyException异常,你可以捕获该异常后做重试或者提示用户操作失败。
内容的提问来源于stack exchange,提问作者user91489
相关产品推荐
相关产品推荐

