You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

EF与Sqlite插入重复键时冲突忽略仍抛并发异常怎么解决

解决EF中唯一键冲突时忽略重复记录的问题

这个问题我之前也碰到过,EF抛出的DBConcurrencyException(实际通常是DbUpdateException包裹着数据库层面的唯一键冲突错误),本质是EF预期插入操作会影响1行数据,但数据库的唯一键约束阻止了重复记录插入,导致实际影响行数为0,所以触发了异常。要实现「插入第一条记录,忽略后续冲突项」的逻辑,你可以根据场景选择下面几种方案:

方案1:插入前提前过滤重复项(低并发场景适用)

在批量插入之前,先查询数据库中已存在的唯一键,然后过滤掉待插入列表中重复的项,只插入不存在的记录。这种方法简单直观,但要注意存在竞态条件——如果在查询和插入之间有其他进程插入了相同的键,还是会触发异常,适合单线程或低并发的场景。

示例代码:

// 先获取数据库中已存在的唯一键
var existingUniqueKeys = await dbContext.YourEntities
    .Select(e => e.YourUniqueKeyColumn)
    .ToListAsync();

// 过滤出待插入列表中不存在的记录
var entitiesToInsert = yourInputList
    .Where(e => !existingUniqueKeys.Contains(e.YourUniqueKeyColumn))
    .ToList();

// 执行插入
dbContext.YourEntities.AddRange(entitiesToInsert);
await dbContext.SaveChangesAsync();

方案2:利用数据库原生语法(高并发场景推荐)

直接使用数据库支持的「忽略重复插入」语法,比如MySQL的INSERT IGNOREINSERT ... ON DUPLICATE KEY UPDATE,或者SQL Server的MERGE语句。这种方法绕过EF的默认跟踪逻辑,直接在数据库层面处理冲突,能有效避免竞态条件,适合高并发场景。

以MySQL的INSERT IGNORE为例,示例代码:

var sqlTemplate = "INSERT IGNORE INTO YourEntities (UniqueKey, Column1, Column2) VALUES ";
var parameters = new List<MySqlParameter>();
var valueClauses = new List<string>();

for (int i = 0; i < yourInputList.Count; i++)
{
    var entity = yourInputList[i];
    // 构造参数化的value子句,避免SQL注入
    valueClauses.Add($"(@Key{i}, @Col1{i}, @Col2{i})");
    parameters.Add(new MySqlParameter($"@Key{i}", entity.UniqueKey));
    parameters.Add(new MySqlParameter($"@Col1{i}", entity.Column1));
    parameters.Add(new MySqlParameter($"@Col2{i}", entity.Column2));
}

// 拼接完整SQL并执行
var finalSql = sqlTemplate + string.Join(", ", valueClauses);
await dbContext.Database.ExecuteSqlRawAsync(finalSql, parameters.ToArray());

如果是SQL Server,可以用MERGE语句实现类似逻辑,判断记录不存在时才插入。

方案3:捕获冲突异常并忽略(灵活但性能略低)

如果不想提前查询或者使用原生SQL,可以尝试捕获插入时的DbUpdateException,判断是否是唯一键冲突,然后忽略该异常。如果是批量插入,建议拆分列表逐个插入,避免因为单个冲突导致整个批量操作失败。

示例代码:

foreach (var entity in yourInputList)
{
    try
    {
        dbContext.YourEntities.Add(entity);
        await dbContext.SaveChangesAsync();
    }
    catch (DbUpdateException ex)
    {
        // 检查是否是唯一键冲突(不同数据库错误码不同)
        // SQL Server的唯一键冲突错误码是2601或2627;MySQL是1062
        var sqlEx = ex.InnerException as SqlException;
        if (sqlEx == null || !(sqlEx.Number == 2601 || sqlEx.Number == 2627))
        {
            // 不是唯一键冲突,重新抛出异常
            throw;
        }
        // 是唯一键冲突,忽略该记录,重置实体状态
        dbContext.Entry(entity).State = EntityState.Detached;
    }
}

注意点

  • 不同数据库的唯一键冲突错误码不同,需要根据你使用的数据库调整判断逻辑。
  • 逐个插入的方式性能会比批量插入低,适合数据量不大的场景。

内容的提问来源于stack exchange,提问作者Sergio M. Pérez Mayo

火山引擎 最新活动