EF Core 默认不支持真正批量插入,AddRange+SaveChanges实为单条插入;应使用Z.EntityFramework.Extensions实现原生批量,性能提升10–100倍,但需注意ID返回、触发器、事务一致性等约束。

EF Core 默认不支持 BulkInsert,别白费劲写循环
EF Core 原生 DbContext.AddRange() + SaveChanges() 看似批量,实则每条记录都生成独立 INSERT 语句,1000 条数据可能触发上千次 round-trip。这不是“批量”,是“假批量”。硬用它做导入或初始化,很容易卡死或超时。
真正批量插入必须绕过 EF Core 的变更跟踪和 SQL 生成逻辑——要么用原生 ADO.NET 手写批量(麻烦),要么引入第三方扩展库。目前最成熟、兼容性最好的选择是 Z.EntityFramework.Extensions(即 Z.BulkExtensions)。
用 Z.BulkExtensions 实现 BulkInsert 的最小可行步骤
它底层调用的是数据库原生批量能力(SQL Server 的 SqlBulkCopy,PostgreSQL 的 COPY,MySQL 的 LOAD DATA INFILE 或批量 INSERT),性能提升可达 10–100 倍。安装和使用非常直接:
- 通过 NuGet 安装:
Z.EntityFramework.Extensions.EFCore(注意:不是旧版EFCore.BulkExtensions,后者已归档且不维护) - 在
DbContext中启用扩展方法:using Z.EntityFramework.Extensions; - 调用
context.BulkInsert(entities),不是AddRange,也不是SaveChanges
示例:
var users = Enumerable.Range(1, 5000)
.Select(i => new User { Name = $"User{i}", Email = $"u{i}@test.com" })
.ToList();
await context.BulkInsertAsync(users); // 异步安全,自动分批
常见踩坑点:主键、自增、触发器、事务一致性
批量插入不是万能胶,几个关键约束必须提前确认:
-
BulkInsert不会返回插入后的自增 ID(除非显式启用Options.IncludeIdentity = true,但仅 SQL Server 支持,且有性能损耗) - 如果表上有
ON INSERT触发器,默认会被跳过(SQL Server 下需设置Options.SqlBulkCopyOptions = SqlBulkCopyOptions.FireTriggers) - 外键约束默认开启校验;若要跳过(比如先插主表再插子表),得设
Options.SqlBulkCopyOptions = SqlBulkCopyOptions.TableLock并确保数据库级约束未启用(不推荐,慎用) - 它不参与 EF Core 当前
DbContext的事务(BeginTransaction),如需强一致性,必须用原生SqlTransaction包裹,或改用BulkInsertAsync的transaction参数传入
替代方案对比:什么时候不该用 BulkInsert
不是所有场景都适合上 BulkInsert:
- 插入量小于 100 条 → 直接用
AddRange+SaveChanges更简单,无额外依赖 - 需要插入后立刻读取生成的 ID 做后续关联 →
BulkInsert不适合,考虑分批SaveChanges或用OUTPUT子句手写 SQL - 目标数据库是 SQLite 或某些云数据库(如 Azure SQL 托管实例限制
SqlBulkCopy)→ Z.BulkExtensions 可能回退为普通 INSERT,性能无优势,此时应查文档确认是否支持原生批量 - 实体含复杂导航属性或影子属性 → BulkInsert 只处理映射到列的字段,忽略导航集合和未映射属性,这点务必在测试中验证结果
批量操作真正的复杂点不在语法,而在数据一致性边界:ID 分配策略、触发器行为、事务隔离级别、错误行的定位与重试机制——这些没法靠一个 BulkInsert 调用自动解决。


















