ASP.NET Web API多并发场景下生成唯一CategoryCode的解决方法
解决ASP.NET Web API并发创建分类时重复CategoryCode的问题
问题本质
这是典型的并发竞争条件:多个请求同时读取数据库中CategoryCode的最大值,各自自增后插入,导致重复值。这个问题和依赖注入无关(默认DbContext是Scoped模式,每个请求对应独立实例),让接口等待所有客户端完全不可行——这会彻底丧失并发能力,严重拖垮系统性能。
可行解决方案
1. 数据库自增列(推荐)
将CategoryCode设为数据库的自增列(IDENTITY),由数据库负责生成唯一值,从根源避免并发冲突。
实体类修改:
public class Category { // 标记为数据库自增生成 [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int CategoryCode { get; set; } public string CategoryName { get; set; } public int BranchId { get; set; } }
接口代码简化:
[HttpPost] [Route("TestAwait")] public async Task<ActionResult<GeneralApiResponse<StoreHistoryResponse>>> TestAwait(TestModel testId) { try { var newCategory = new Category() { BranchId = 1 }; await _context.Categories.AddAsync(newCategory); await _context.SaveChangesAsync(); // SaveChanges后,数据库生成的CategoryCode会自动赋值到实体 newCategory.CategoryName = $"Test {newCategory.CategoryCode}"; await _context.SaveChangesAsync(); // 若需要更新名称则执行这一步 return Ok(newCategory); } catch (Exception ex) { return BadRequest(ex.InnerException?.Message ?? ex.Message); } }
这种方式性能最高,数据库原生支持并发安全,无需手动处理锁逻辑。
2. 事务+数据库锁(适用于无法修改自增列的场景)
用事务包裹读取和插入操作,同时通过SQL锁机制保证同一时间只有一个请求能读取最大值并插入新数据。
[HttpPost] [Route("TestAwait")] public async Task<ActionResult<GeneralApiResponse<StoreHistoryResponse>>> TestAwait(TestModel testId) { using var transaction = await _context.Database.BeginTransactionAsync(); try { // 使用UPDLOCK+HOLDLOCK锁定表,防止其他事务读取未提交的最大值 var categoryCode = await _context.Categories .FromSqlRaw("SELECT MAX(CategoryCode) FROM Categories WITH (UPDLOCK, HOLDLOCK)") .FirstOrDefaultAsync(); // 处理空表情况,默认从1开始 var newCategoryCode = categoryCode.HasValue ? categoryCode.Value + 1 : 1; await Task.Delay(20000); // 测试用,实际可移除 var newCategory = new Category() { CategoryName = $"Test {newCategoryCode}", CategoryCode = newCategoryCode, BranchId = 1 }; await _context.Categories.AddAsync(newCategory); await _context.SaveChangesAsync(); await transaction.CommitAsync(); return Ok(newCategory); } catch (Exception ex) { await transaction.RollbackAsync(); return BadRequest(ex.InnerException?.Message ?? ex.Message); } }
注意:这种方式会降低并发性能,因为锁会让请求串行处理,适合并发量不高的场景。
3. 分布式锁(多实例部署场景)
如果Web API是多服务器部署,数据库锁可能无法跨实例生效,此时可以用分布式锁(比如基于Redis的RedLock、数据库全局锁表)。实现逻辑是:在读取最大值前先获取分布式锁,操作完成后释放锁,保证跨实例的并发安全。这种方式复杂度较高,仅在多实例且无法使用自增列时考虑。
内容的提问来源于stack exchange,提问作者Ninja Speed
相关产品推荐
相关产品推荐

