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

EF Core 7如何解决并发场景下插入或更新的主键冲突问题?

解决EF Core 7并发场景下主键冲突的UPSERT方案

方案1:使用数据库原生UPSERT语句(推荐,原子操作)

对于SQL Server,可直接使用MERGE语句实现原子性的插入或更新,从数据库层面彻底避免竞态条件。示例代码:

var sql = @"
MERGE dbo.Visitors AS Target
USING (VALUES (@Id, @Url, @Source)) AS Source (Id, Url, Source)
ON Target.Id = Source.Id
WHEN MATCHED THEN
    UPDATE SET 
        Url = Source.Url,
        Source = Source.Source
        -- 补充其他需要更新的属性
WHEN NOT MATCHED THEN
    INSERT (Id, Url, Source)
    VALUES (Source.Id, Source.Url, Source.Source);
";

await db.Database.ExecuteSqlAsync(sql, 
    new SqlParameter("@Id", entity.Id),
    new SqlParameter("@Url", entity.Url),
    new SqlParameter("@Source", entity.Source)
    // 补充对应参数
);

这种方式直接在数据库端完成原子操作,不会出现并发冲突,是性能和可靠性最优的方案。

方案2:尝试更新后插入+异常捕获

先尝试更新记录,若未找到匹配记录则执行插入;若插入时触发主键冲突,捕获异常后再次尝试更新。这种方式利用异常处理兜底,适用于不想编写原生SQL的场景:

try
{
    var updatedCount = await db.Visitors
        .Where(v => v.Id == entity.Id)
        .ExecuteUpdateAsync(setters => setters
            .SetProperty(v => v.Url, entity.Url)
            .SetProperty(v => v.Source, entity.Source)
            // 补充其他属性更新逻辑
        );

    if (updatedCount == 0)
    {
        db.Visitors.Add(entity);
        await db.SaveChangesAsync();
    }
}
catch (DbUpdateException ex) when (ex.InnerException is SqlException sqlEx && sqlEx.Number == 2627)
{
    // 捕获SQL Server主键冲突错误(错误码2627),再次执行更新
    await db.Visitors
        .Where(v => v.Id == entity.Id)
        .ExecuteUpdateAsync(setters => setters
            .SetProperty(v => v.Url, entity.Url)
            .SetProperty(v => v.Source, entity.Source)
            // 补充其他属性更新逻辑
        );
}

注意:不同数据库的主键冲突错误码不同,需根据使用的数据库调整判断条件。

方案3:提升事务隔离级别

之前使用事务无效是因为默认ReadCommitted隔离级别下,后续事务无法读取未提交的新记录。可将隔离级别提升为RepeatableRead或Serializable,强制后续事务等待前一个事务完成后再执行查询:

using var transaction = await db.Database.BeginTransactionAsync(System.Data.IsolationLevel.RepeatableRead);
try
{
    var entityDB = await db.Visitors.FindAsync(entity.Id);

    if (entityDB is not null)
    {
        entityDB.Url = entity.Url;
        entityDB.Source = entity.Source;
        // 补充其他属性更新逻辑
    }
    else
    {
        db.Visitors.Add(entity);
    }

    await db.SaveChangesAsync();
    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}

此方式会降低并发性能,因为事务会锁住资源导致后续请求等待,仅适用于并发量较低的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 18:13:03