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 IGNORE、INSERT ... 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




