You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 10:20:05