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

EF Core复合键中counterKey按matchId与ownerId分组自增实现问询

实现方案

你可以通过两种主流方案实现该需求,分别对应应用层实现和数据库层实现:

方案1:EF Core应用层实现(适配低并发场景)

通过重写DbContext的保存方法,在插入数据前查询同分组的最大counterKey后自增赋值,代码实现如下:

  1. 首先配置实体的联合主键(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();
}
  1. 重写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为例实现如下:

  1. 数据库端创建插入触发器:
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
  1. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 20:15:02