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

EF Core插入记录失败但手动插入成功的原因排查

问题描述

我有一个SQLite数据库,包含如下Orders表:

CREATE TABLE "Orders"
(
    "ID"          INTEGER NOT NULL,
    "Title"       TEXT NOT NULL,
    "Description" TEXT,
    "Price"       REAL NOT NULL,
    "WarehouseID" INTEGER NOT NULL,
    "Category"    TEXT,

    FOREIGN KEY("WarehouseID") 
         REFERENCES "Warehouses"("ID") 
             ON DELETE CASCADE ON UPDATE CASCADE,
    PRIMARY KEY("ID" AUTOINCREMENT),
    UNIQUE("Title", "Description", "Price", "WarehouseID", "Category") ON CONFLICT IGNORE
)

使用Microsoft.EntityFrameworkCore.Sqlite 9.0.2,通过以下代码批量添加订单:

public static void SaveOrders(IEnumerable<Order> orders)
{
    dbContext.Orders.AddRange(orders);
    dbContext.SaveChanges();
}

该方法被多次调用,每次传入约100条订单。初始插入1310条记录正常,之后抛出DbUpdateConcurrencyException,提示EF Core预期影响1行但实际影响0行。

已知可能是插入违反唯一约束(SQLite的ON CONFLICT IGNORE会忽略操作并返回0行受影响),但手动执行EF Core日志中的插入命令却能成功添加记录。EF Core执行的命令如下:

Executing DbCommand [Parameters=[@p0='Sales', @p1='', @p2='24.90', @p3='16', @p4='iPhone XS 15 extra features camera, sleeve' (Size = 43)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Orders" ("Category", "Description", "Price", "WarehouseID", "Title")
VALUES (@p0, @p1, @p2, @p3, @p4)
RETURNING "ID";

手动执行带RETURNING "ID"的命令时,SQLite返回0行且记录未插入;去掉RETURNING "ID"的命令可成功插入,后续执行两种命令都会触发唯一约束失败。

补充排查:源数据中有两条完全相同的订单,调用AddRange添加时,EF Core在插入第一条就报错说违反唯一约束,但此时数据库中并无该记录,手动插入却能成功。

问题分析与解决方案

核心原因

  1. EF Core与SQLite ON CONFLICT IGNORE的兼容性冲突
    SQLite的ON CONFLICT IGNORE会静默忽略违反约束的插入操作,仅返回0行受影响;但EF Core执行带RETURNING "ID"的插入时,预期会获取自增ID值,一旦返回0行,就会判定为并发冲突,抛出DbUpdateConcurrencyException。

  2. EF Core内存上下文的预检测机制
    当调用AddRange传入两条完全相同的订单时,EF Core会先在内存跟踪集合中检查实体唯一性(基于唯一约束字段匹配)。即使数据库中无该记录,只要内存中已存在待插入的同一条实体,EF Core会直接判定违反唯一约束,提前抛出错误,不会走到数据库执行阶段。

解决办法

  1. 关闭客户端唯一约束预检查
    在DbContext的模型配置中,针对Orders表的唯一约束关闭客户端验证,让冲突判定完全交给数据库:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>()
            .HasIndex(o => new { o.Title, o.Description, o.Price, o.WarehouseID, o.Category })
            .IsUnique()
            .HasAnnotation("Sqlite:ConflictClause", "IGNORE")
            .HasAnnotation("Validation:Skip", true);
    }
    
  2. 捕获并忽略冲突异常
    在SaveChanges时针对性捕获异常,忽略因ON CONFLICT IGNORE或重复实体导致的报错:

    public static void SaveOrders(IEnumerable<Order> orders)
    {
        dbContext.Orders.AddRange(orders);
        try
        {
            dbContext.SaveChanges();
        }
        catch (DbUpdateConcurrencyException)
        {
            // 忽略因ON CONFLICT IGNORE导致的0行受影响异常
        }
        catch (DbUpdateException ex)
        {
            if (ex.InnerException is SQLiteException sqliteEx && sqliteEx.SqliteErrorCode == 19)
            {
                // 忽略SQLite唯一约束冲突错误(错误码19)
                return;
            }
            throw;
        }
    }
    
  3. 批量插入前内存去重
    在传入AddRange前,先对订单列表按唯一约束字段去重,避免重复实体进入上下文:

    var distinctOrders = orders.Distinct(new OrderEqualityComparer()).ToList();
    dbContext.Orders.AddRange(distinctOrders);
    

    自定义相等比较器实现:

    public class OrderEqualityComparer : IEqualityComparer<Order>
    {
        public bool Equals(Order x, Order y)
        {
            if (x == null || y == null) return false;
            return x.Title == y.Title
                && x.Description == y.Description
                && x.Price == y.Price
                && x.WarehouseID == y.WarehouseID
                && x.Category == y.Category;
        }
    
        public int GetHashCode(Order obj)
        {
            return HashCode.Combine(obj.Title, obj.Description, obj.Price, obj.WarehouseID, obj.Category);
        }
    }
    
  4. 调整EF Core插入行为,避免依赖RETURNING
    配置EF Core使用last_insert_rowid()替代RETURNING获取自增ID,适配SQLite的ON CONFLICT IGNORE逻辑:

    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder.Properties<int>()
            .HaveColumnType("INTEGER")
            .UseIdentityColumn();
        configurationBuilder.UseSqliteIdentityColumns();
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 22:02:07