EF Core复合键中counterKey按matchId与ownerId分组自增实现问询
实现方案
你可以通过两种主流方案实现该需求,分别对应应用层实现和数据库层实现:
方案1:EF Core应用层实现(适配低并发场景)
通过重写DbContext的保存方法,在插入数据前查询同分组的最大counterKey后自增赋值,代码实现如下:
- 首先配置实体的联合主键(EF Core要求显式声明联合主键,仅靠
Column(Order)无法生效):
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置联合主键 modelBuilder.Entity<Turn>() .HasKey(t => new { t.ownerId, t.matchId, t.counterKey }); // 可选:添加唯一约束避免并发冲突 modelBuilder.Entity<Turn>() .HasIndex(t => new { t.matchId, t.ownerId, t.counterKey }) .IsUnique(); }
- 重写DbContext的
SaveChanges和SaveChangesAsync方法,自动赋值counterKey:
public override int SaveChanges() { var newTurnEntries = ChangeTracker.Entries<Turn>() .Where(e => e.State == EntityState.Added); foreach (var entry in newTurnEntries) { var turn = entry.Entity; // 查询当前<matchId, ownerId>分组的最大counterKey var currentMax = Set<Turn>() .Where(t => t.matchId == turn.matchId && t.ownerId == turn.ownerId) .Max(t => (int?)t.counterKey) ?? 0; turn.counterKey = currentMax + 1; } return base.SaveChanges(); } public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { var newTurnEntries = ChangeTracker.Entries<Turn>() .Where(e => e.State == EntityState.Added); foreach (var entry in newTurnEntries) { var turn = entry.Entity; var currentMax = await Set<Turn>() .Where(t => t.matchId == turn.matchId && t.ownerId == turn.ownerId) .MaxAsync(t => (int?)t.counterKey, cancellationToken) ?? 0; turn.counterKey = currentMax + 1; } return await base.SaveChangesAsync(cancellationToken); }
注意:该方案存在并发冲突风险,同一时间插入多条同分组数据时可能生成重复的counterKey,适合并发量较低的业务场景,若出现冲突可以捕获唯一约束异常后重试插入。
方案2:数据库层实现(适配高并发场景)
通过数据库触发器自动生成counterKey,完全避免应用层并发问题,以SQL Server为例实现如下:
- 数据库端创建插入触发器:
CREATE TRIGGER trg_Turn_AutoIncrementCounterKey ON Turn AFTER INSERT AS BEGIN SET NOCOUNT ON; UPDATE t SET t.counterKey = ISNULL((SELECT MAX(counterKey) FROM Turn WHERE matchId = i.matchId AND ownerId = i.ownerId), 0) + 1 FROM Turn t INNER JOIN inserted i ON t.ownerId = i.ownerId AND t.matchId = i.matchId -- 仅处理新增的未赋值counterKey的行 WHERE t.counterKey = 0; END
- EF Core配置
counterKey为插入时数据库生成:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Turn>() .HasKey(t => new { t.ownerId, t.matchId, t.counterKey }); // 配置counterKey为数据库生成,插入后自动读取返回值 modelBuilder.Entity<Turn>() .Property(t => t.counterKey) .ValueGeneratedOnAdd(); modelBuilder.Entity<Turn>() .HasIndex(t => new { t.matchId, t.ownerId, t.counterKey }) .IsUnique(); }
该方案性能更稳定,不会出现重复值,适合高并发业务场景。
内容的提问来源于stack exchange,提问作者Jaeger
相关产品推荐
相关产品推荐

