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

ASP.NET Core EF Code-First中实现无重复递增库存请求编号的方案咨询

实现数据库唯一且基于最新记录递增的请求编号方案

问题分析

当前模型里的静态变量_nextSequenceNumber存在致命问题:它是应用域级别的内存变量,应用重启、多实例部署(集群/负载均衡)时会重置或出现多实例各自计数,必然导致重复编号;而且完全没有读取数据库现有记录的最大编号,生成的序列和数据库实际状态完全脱节。

核心实现思路

要保证编号唯一且正确递增,必须从数据库获取当前最新的序列值,而非依赖内存中的静态变量。同时要处理并发场景下的冲突(比如两个请求同时读取到相同最大值,进而生成重复编号)。

具体实现方案

方案1:数据库查询+事务(中小并发场景适用)

在创建新记录前,先查询数据库中当前年月下的最大序列编号,加1后生成新编号,并用事务包裹查询和插入操作,保证原子性。

示例代码(基于EF Core):

public async Task<InventoryRequest> CreateInventoryRequestAsync(InventoryRequest newRequest)
{
    const string fixedString = "31751/PL.711/";
    var currentMonth = DateTime.Today.Month;
    var currentYear = DateTime.Today.Year;

    // 开启事务,确保查询和插入操作原子性
    using var transaction = await _dbContext.Database.BeginTransactionAsync();
    try
    {
        // 查询当前年月下的最新请求记录
        var latestRequest = await _dbContext.InventoryRequests
            .Where(r => r.RequestNumber != null 
                && r.RequestNumber.StartsWith("B-") 
                && r.RequestNumber.EndsWith($"/{fixedString}{currentMonth}/{currentYear}"))
            .OrderByDescending(r => r.RequestNumber)
            .FirstOrDefaultAsync();

        int nextSeq = 1;
        if (latestRequest != null)
        {
            // 从现有编号中解析出序列值:比如"B-005/..." 提取005转为int
            var seqSegment = latestRequest.RequestNumber.Split('/')[0].Split('-')[1];
            if (int.TryParse(seqSegment, out int lastSeq))
            {
                nextSeq = lastSeq + 1;
            }
        }

        // 生成新编号
        newRequest.RequestNumber = $"B-{nextSeq:D3}/{fixedString}{currentMonth}/{currentYear}";
        newRequest.RequestDate = DateTime.Today;

        _dbContext.InventoryRequests.Add(newRequest);
        await _dbContext.SaveChangesAsync();
        await transaction.CommitAsync();

        return newRequest;
    }
    catch
    {
        await transaction.RollbackAsync();
        throw;
    }
}

注意:

  • 按年月筛选查询,避免跨年月的序列干扰(比如去年12月最大编号为100,今年1月应从1开始计数)
  • 事务能大幅降低并发场景下的重复概率

方案2:数据库序列(高并发场景适用)

如果使用SQL Server、PostgreSQL等支持序列(Sequence)的数据库,可以让数据库负责生成唯一递增的序列值,从根源避免冲突。

以SQL Server为例:

  1. 创建全局序列(或按年月创建独立序列):
CREATE SEQUENCE InventoryRequestSeq
    START WITH 1
    INCREMENT BY 1
    NO CYCLE;
  1. 在EF Core中调用序列生成值:
public async Task<InventoryRequest> CreateInventoryRequestAsync(InventoryRequest newRequest)
{
    const string fixedString = "31751/PL.711/";
    var currentMonth = DateTime.Today.Month;
    var currentYear = DateTime.Today.Year;

    // 从数据库获取下一个序列值
    var nextSeq = await _dbContext.Database.SqlQuery<int>("SELECT NEXT VALUE FOR InventoryRequestSeq").FirstAsync();
    
    newRequest.RequestNumber = $"B-{nextSeq:D3}/{fixedString}{currentMonth}/{currentYear}";
    newRequest.RequestDate = DateTime.Today;

    _dbContext.InventoryRequests.Add(newRequest);
    await _dbContext.SaveChangesAsync();

    return newRequest;
}

若需要按年月重置序列,可在每月初执行重置脚本,或创建动态命名的序列(比如InventoryRequestSeq_202409),每月自动生成新序列。

方案3:唯一约束兜底

无论采用哪种方案,都要给RequestNumber字段添加数据库唯一约束,防止极端情况下的重复数据入库:
在EF Core模型配置中添加:

modelBuilder.Entity<InventoryRequest>()
    .HasIndex(r => r.RequestNumber)
    .IsUnique();

这样即使出现意外情况,数据库会直接抛出唯一约束冲突异常,阻止重复数据写入。

必须废弃原构造函数逻辑

原构造函数中的静态变量逻辑完全不适合多实例、持久化场景,会导致大量重复编号问题,必须删除该部分代码。

内容的提问来源于stack exchange,提问作者Haminteu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 18:07:02