EF Core 批量操作实战:3 种方案对比与性能测试
本文将对比 3 种 EF Core 批量操作方案:
- 第三方库(EFCore.BulkExtensions):简单高效,开箱即用;
- 原生 SQL+Dapper:灵活可控,适合复杂批量逻辑;
- EF Core 原生事务 + AddRange:无依赖,适合小数据量场景;
并通过真实性能测试,告诉你不同数据量下该选哪种方案。
一、环境准备
1. 基础配置
- .NET 版本:.NET 10
- EF Core
- 虚拟机+数据库:SQL Server 2019
// 实体模型 public class User { /// <summary> /// 主键 /// </summary> public int Id { get; set; } /// <summary> /// 用户名 /// </summary> public string UserName { get; set; } = string.Empty; /// <summary> /// 创建时间 /// </summary> public DateTime CreateTime { get; set; } = DateTime.Now; }
2. 测试数据生成工具
封装一个生成测试数据的方法,方便后续批量测试:
public static List<User> GenerateTestUsers(int count) { var users = new List<User>(); for (int i = 0; i < count; i++) { users.Add(new User { UserName = $"TestUser_{Guid.NewGuid():N}", CreateTime = DateTime.Now }); } return users; }
二、三种批量操作方案实现
方案 1:EFCore.BulkExtensions(推荐,高效简洁)
这是 EF Core 生态中最成熟的批量操作库,支持批量新增、更新、删除、合并,底层用 Bulk Insert/Update 实现,性能接近原生 SQL。
/// <summary> /// 方案1:EFCore.BulkExtensions批量操作 /// </summary> /// <param name="users">待插入数据</param> public static async Task BulkInsertWithExtension(List<User> users) { using var dbContext = new AppDbContext(); // 记录开始时间 var stopwatch = Stopwatch.StartNew(); // 批量插入核心代码 await dbContext.BulkInsertAsync(users); stopwatch.Stop(); Console.WriteLine($"EFCore.BulkExtensions插入{users.Count}条数据耗时:{stopwatch.ElapsedMilliseconds}ms"); }
方案 2:原生 SQL+Dapper(灵活,适合复杂场景)
Dapper 是轻量 ORM,执行原生 SQL 的性能几乎和ADO.NET持平,适合需要自定义批量 SQL 的场景(比如批量更新带复杂条件)。
/// <summary> /// 方案2:原生SQL+Dapper批量插入 /// </summary> /// <param name="users">待插入数据</param> public static async Task BulkInsertWithDapper(List<User> users) { using var connection = new SqlConnection(Common.ConnectionString); await connection.OpenAsync(); var stopwatch = Stopwatch.StartNew(); // 批量插入SQL(表值参数方式,避免SQL注入) var sql = @"INSERT INTO Users (UserName, CreateTime) SELECT UserName, CreateTime FROM @Users;"; // 构造表值参数 var dataTable = new DataTable(); dataTable.Columns.Add("UserName", typeof(string)); dataTable.Columns.Add("CreateTime", typeof(DateTime)); foreach (var user in users) { dataTable.Rows.Add(user.UserName, user.CreateTime); } await connection.ExecuteAsync(sql, new { Users = dataTable.AsTableValuedParameter("dbo.UserTableType") }); stopwatch.Stop(); Console.WriteLine($"Dapper+原生SQL插入{users.Count}条数据耗时:{stopwatch.ElapsedMilliseconds}ms"); }
注意:需要先在 SQL Server 中创建表值类型UserTableType:
CREATE TYPE UserTableType AS TABLE ( UserName NVARCHAR(50), CreateTime DATETIME );
方案 3:EF Core 原生事务 + AddRange(无依赖,小数据量)
这是 EF Core 原生支持的方案,无需安装任何第三方库,但本质还是逐条生成 SQL(只是通过事务减少提交次数),适合小数据量场景。
public static async Task BulkInsertWithEFNative(List<User> users) { using var dbContext = new AppDbContext(); // 开启事务 using var transaction = await dbContext.Database.BeginTransactionAsync(); var stopwatch = Stopwatch.StartNew(); // 批量添加到上下文 dbContext.Users.AddRange(users); // 一次性提交 await dbContext.SaveChangesAsync(); // 提交事务 await transaction.CommitAsync(); stopwatch.Stop(); if (users != null && users.Count >= 100) Console.WriteLine($"EF Core原生AddRange插入{users.Count}条数据耗时:{stopwatch.ElapsedMilliseconds}ms"); }

- 优先选 EFCore.BulkExtensions:90% 的批量场景都适用,代码简洁、性能接近原生 SQL;
- 选 Dapper + 原生 SQL:需要自定义复杂批量逻辑(比如批量更新带多条件)、或对第三方库有顾虑;
当数据表有变动时,修改困难。 - 选 EF Core 原生 AddRange:数据量 < 1000、且不想引入任何第三方依赖。
石家庄的.net程序员


浙公网安备 33010602011771号