EF Core中如何为数据库表多列设置并发令牌?附重复收费疑问
月度平台费防重复收费问题解答
问题1:能否为ReceiverId和Date设置ConcurrencyToken来触发重复插入的并发异常?
不行。并发令牌(ConcurrencyToken)的设计目的是处理更新/删除操作的并发冲突,它的工作逻辑是在更新时对比数据库中当前的令牌值和实体加载时的令牌值是否一致。而插入操作时,数据库中还没有对应记录,EF Core不会触发并发冲突检测,也就不会抛出DbUpdateConcurrencyException。
你当前代码里的判断和异常捕获逻辑是无效的:即使两个请求同时通过hasPaidMonthlyFee的检查去插入,也只会触发数据库的唯一约束冲突(如果没加唯一键的话甚至会插入重复数据),不会抛出并发异常。
问题2:应该用并发令牌、唯一键,还是两者都用?
必须用数据库复合唯一键,不需要用并发令牌,原因如下:
- 唯一键是在数据库层面强制
ReceiverId+Date的唯一性,彻底杜绝重复插入,哪怕出现多个请求同时绕过应用层检查的竞态场景,数据库也会直接拒绝重复数据。 - 并发令牌在这里没有用武之地,因为你的核心需求是防止重复插入,而非处理同一记录的并发更新/删除。
具体实现步骤
添加复合唯一索引
在EF Core中可以通过Fluent API配置ReceiverId和Date的复合唯一约束:protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<PlatformFee>() .HasIndex(p => new { p.ReceiverId, p.Date }) .IsUnique(); }执行迁移后,数据库会为这两列创建唯一索引,插入重复数据时会抛出
DbUpdateException。修正业务逻辑
去掉无效的并发异常捕获,改为捕获唯一键冲突:var hasPaidMonthlyFee = await _dbContext.PlatformFee .AnyAsync(p => p.ReceiverId == id && p.Date == 0624); if (!hasPaidMonthlyFee) { try { _dbContext.PlatformFee.Add(platformFee); await _dbContext.SaveChangesAsync(); // 执行扣费后续逻辑 } catch(DbUpdateException ex) { // 检查是否是唯一键冲突(不同数据库的错误码不同,这里以SQL Server为例) if (ex.InnerException is SqlException sqlEx && sqlEx.Number == 2601) { // 处理重复收费的情况,比如返回已收费提示 } } }注意:应用层的
AnyAsync检查是为了减少不必要的数据库插入操作,但不能完全依赖它,必须配合数据库的唯一约束来处理并发场景。
内容的提问来源于stack exchange,提问作者chuckd
相关产品推荐
相关产品推荐

