EF Core 批量操作实战:3 种方案对比与性能测试

本文将对比 3 种 EF Core 批量操作方案:
  1. 第三方库(EFCore.BulkExtensions):简单高效,开箱即用;
  2. 原生 SQL+Dapper:灵活可控,适合复杂批量逻辑;
  3. 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");
        }

TestSQL1

方案 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
);

TestSQL2

方案 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");
        }

TestSQL3

  1. 优先选 EFCore.BulkExtensions:90% 的批量场景都适用,代码简洁、性能接近原生 SQL;
  2. 选 Dapper + 原生 SQL:需要自定义复杂批量逻辑(比如批量更新带多条件)、或对第三方库有顾虑;
    当数据表有变动时,修改困难。
  3. 选 EF Core 原生 AddRange:数据量 < 1000、且不想引入任何第三方依赖。
 
posted @ 2026-02-01 11:24  燕闪硕  阅读(1)  评论(0)    收藏  举报