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

EF Core 6如何实现字母数字属性自增解决并发重复问题

问题根因

你当前的实现逻辑是先查询全表交易记录总数,再基于总数拼接生成编码,整个过程没有任何并发控制:多个并发请求会同时读到相同的计数值,最终生成重复的ReferenceCode,且没有数据库层面的唯一约束兜底,重复数据会直接写入库中。
以下是适配EF Core 6 + PostgreSQL Code First模式的可靠落地方案,按推荐优先级排序:

方案1:PostgreSQL原生序列+计算列(首推,性能最高,无并发冲突)

PostgreSQL的序列对象本身是数据库层面实现的原子递增结构,并发请求调用序列取值时永远不会拿到重复值,配合计算列可以完全把编码生成逻辑下沉到数据库,不需要业务代码写计数逻辑。

实现步骤

在你的DbContext的OnModelCreating方法中做如下配置:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 定义交易编码专用序列,从1开始递增,步长为1
    modelBuilder.HasSequence<int>("TransactionRefSequence")
        .StartsAt(1)
        .IncrementsBy(1);

    // 配置ReferenceCode为数据库自动生成的计算列
    modelBuilder.Entity<Transaction>()
        .Property(t => t.ReferenceCode)
        // 自动拼接TR前缀,按5位数字补零,可根据需要调整补零位数(比如你之前用的10位就填FM0000000000)
        .HasComputedColumnSql("'TR' || to_char(nextval('\"TransactionRefSequence\"'), 'FM00000')", stored: true)
        .IsRequired();

    // 兜底:给ReferenceCode加唯一索引,极端情况也不会插入重复值
    modelBuilder.Entity<Transaction>()
        .HasIndex(t => t.ReferenceCode)
        .IsUnique();
}

注意事项

  • 业务代码创建Transaction实体时,不需要手动给ReferenceCode赋值,数据库在插入记录时会自动生成符合格式的编码,SaveChanges完成后EF Core会自动把生成的编码回填到实体属性中。
  • 序列特性决定了如果插入事务回滚,已经取出的序列值不会回退,可能出现编码跳号的情况,这对交易类业务属于正常现象(交易单号本身不要求严格连续,仅要求唯一可追溯)。
  • 该方案无锁等待,并发写入性能远高于业务层计数方案。

方案2:序号跟踪表+行级排他锁(适用于要求编码严格连续的场景)

如果你的业务要求交易编码必须严格连续、不允许跳号,可以用独立序号表加数据库行锁的方式保证取值原子性。

实现步骤

  1. 先定义序号跟踪实体:
public class TransactionSequence
{
    public int Id { get; set; }
    public int CurrentValue { get; set; }
}
  1. 配置表结构后,预先插入一条初始化记录,CurrentValue设为0。
  2. 生成编码、创建交易的逻辑放到事务中执行:
using var dbTransaction = await applicationDbContext.Database.BeginTransactionAsync();
// 加FOR UPDATE排他锁查询当前序号,查询期间其他并发请求会阻塞等待锁释放,不会读到相同值
int nextValue = await applicationDbContext.Database
    .SqlQueryRaw<int>("SELECT \"CurrentValue\" + 1 FROM \"TransactionSequence\" FOR UPDATE")
    .FirstAsync();
// 更新序号值
await applicationDbContext.Database.ExecuteSqlRawAsync(
    "UPDATE \"TransactionSequence\" SET \"CurrentValue\" = {0}", nextValue
);
// 拼接生成编码
string referenceCode = "TR" + nextValue.ToString("00000");
// 按业务逻辑创建Transaction实体,给ReferenceCode赋值
var transaction = new Transaction(/* 构造参数传referenceCode */);
applicationDbContext.Transactions.Add(transaction);
await applicationDbContext.SaveChangesAsync();
await dbTransaction.CommitAsync();

注意事项

  • 该方案能保证编码严格连续,但并发写入时会因为锁等待降低吞吐量,仅适合并发量不高、对连续性有强要求的场景。
  • 同样需要给ReferenceCode配置唯一索引做兜底。

不推荐的方案

不要尝试用应用层内存锁、分布式锁解决该问题:锁本身会增加系统复杂度,一旦锁失效还是会出现重复编码,且性能不如数据库原生方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 23:51:30