EF Core 批量插入操作原理分析

概要

本文主要讨论EF Core 在批量添加操作的基本原理和优化方式,基本原理主要针对EF Core 6.0 和7.0两个版本。

代码和实现

本文通过一个简单场景来模拟批量添加操作。一个分行(Branch)包含若干台ATM机,Branch和ATM各对应一张数据表。我们一次添加多台ATM机的数据到ATM机数据表。

具体Branch和ATM的定义请参考附录

批量添加代码如下:

csharp 复制代码
 public async Task<int> BatchAddATMs()
 {
     await _context.Set<ATM>().AddRangeAsync(new List<ATM>()
     {
         new ATM(){ Name="ATM1",BranchId = 1 },
         new ATM(){ Name="ATM2", BranchId = 1},
         new ATM(){ Name="ATM3", BranchId = 1},
         new ATM(){ Name="ATM4", BranchId = 1},
         new ATM(){ Name="ATM5", BranchId = 1},
         new ATM(){Name="ATM6" , BranchId = 2},
         new ATM(){Name="ATM7" , BranchId = 2}
     });
     return await _context.SaveChangesAsync(); 
 }

代码执行后,产生的SQL语句如下:

sql 复制代码
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (396ms) [Parameters=[@p0='?' (DbType = Int32), @p1='?'
(DbType = Int32), @p2='?' (DbType = Boolean), @p3='?' (Size = 4000), @p4='?' (Db
Type = Boolean), @p5='?' (DbType = Int32), @p6='?' (DbType = Int32), @p7='?' (Db
Type = Boolean), @p8='?' (Size = 4000), @p9='?' (DbType = Boolean), @p10='?' (Db
Type = Int32), @p11='?' (DbType = Int32), @p12='?' (DbType = Boolean), @p13='?'
(Size = 4000), @p14='?' (DbType = Boolean), @p15='?' (DbType = Int32), @p16='?'
(DbType = Int32), @p17='?' (DbType = Boolean), @p18='?' (Size = 4000), @p19='?'
(DbType = Boolean), @p20='?' (DbType = Int32), @p21='?' (DbType = Int32), @p22='
?' (DbType = Boolean), @p23='?' (Size = 4000), @p24='?' (DbType = Boolean), @p25
='?' (DbType = Int32), @p26='?' (DbType = Int32), @p27='?' (DbType = Boolean), @
p28='?' (Size = 4000), @p29='?' (DbType = Boolean), @p30='?' (DbType = Int32), @
p31='?' (DbType = Int32), @p32='?' (DbType = Boolean), @p33='?' (Size = 4000), @
p34='?' (DbType = Boolean)], CommandType='Text', CommandTimeout='30']
      SET IMPLICIT_TRANSACTIONS OFF;
      SET NOCOUNT ON;
      MERGE [tt_atm] USING (
      VALUES (@p0, @p1, @p2, @p3, @p4, 0),
      (@p5, @p6, @p7, @p8, @p9, 1),
      (@p10, @p11, @p12, @p13, @p14, 2),
      (@p15, @p16, @p17, @p18, @p19, 3),
      (@p20, @p21, @p22, @p23, @p24, 4),
      (@p25, @p26, @p27, @p28, @p29, 5),
      (@p30, @p31, @p32, @p33, @p34, 6)) AS i ([BranchId], [DeviceStatus], [IsDe
leted], [Name], [SupportForeignCurrency], _Position) ON 1=0
      WHEN NOT MATCHED THEN
      INSERT ([BranchId], [DeviceStatus], [IsDeleted], [Name], [SupportForeignCu
rrency])
      VALUES (i.[BranchId], i.[DeviceStatus], i.[IsDeleted], i.[Name], i.[Suppor
tForeignCurrency])
      OUTPUT INSERTED.[Id], INSERTED.[Rowversion], i._Position;

我们可以看到EF Core 并没有为每个ATM生成一条insert语句去执行插入操作,而是使用了merge语句。

merge语句主要用于源数据表和目标数据表的数据同步,它本身就包含了插入,更新和删除操作,具备出色的性能。

在本例中,要使用merge语句来批量操作,EF Core 分两步来实现:

  1. 使用Derived Table来模拟源数据表,将要插入的数据通过values子句,创建出一张源数据表;
  2. ON 1=0是人工制造一个when not matched成立的条件,即只执行insert操作,忽略merge中的update和delete操作。

7.0版本相比于6.0版本,更加高效。7.0中删除了事务操作,因为merge是受隐式事务保护的单个语句。而且7.0不再使用临时表作为源数据表,而是使用Derived Table来作为源数据表。

请注意,该批量插入的方法存在一个缺点,我们可以看到生成的SQL包含OUTPUT 语句,所以如果目标数据表包含触发器,则会抛出异常。

附录

csharp 复制代码
 [Table("tt_branch")]
    public class Branch : Entity
    {
        [Required]
        public string Name { get; set; } = string.Empty;
        [Required]
        public string Address { get; set; } = string.Empty;
        [Required]
        public bool hasCreditCardService { get; set; } = false;
        [Required]
        public bool hasChequeService { get; set; } = false;

        public ICollection<ATM> Atms { get; } = new List<ATM>();
        public ICollection<McDonaldATM> MCAtms { get; } = new List<McDonaldATM>();
        public ICollection<CDM> Cdms { get; } = new List<CDM>();

        public User Manager { get; set; } = null!;

}
public abstract class BankDevice : Entity
    {
       
        [Required]
        public string Name { get; set; } = string.Empty;
        [Required]
        public DeviceStatus DeviceStatus { get; set; } = DeviceStatus.Running;
        public int BranchId { get; set; }
    }
    [Table("tt_atm")]
    public class ATM : BankDevice
    {
        [Required]
        public bool SupportForeignCurrency { get; set; } = false;

    }
相关推荐
运维行者_5 小时前
Applications Manager中的Redis监控
大数据·服务器·数据库·人工智能·网络协议
悦数图数据库8 小时前
图数据库选型指南 2026:从架构、性能、AI 适配三个维度看 悦数科技
数据库·人工智能·架构
handler019 小时前
【MySQL】常用命令总结(库与表增删查改)
运维·数据库·mysql·命令·总结
week@eight9 小时前
Linux - Doris
linux·运维·数据库·mysql
cdbqss110 小时前
VB2026 菜单生成基类 BqGetMenuStrip
数据库·经验分享·学习·oracle·vb
洛水水10 小时前
Redis 分布式锁详解:实现与缺陷
数据库·redis·分布式
韶博雅10 小时前
oracle中表和列转大写
数据库·oracle
暴躁小师兄数据学院11 小时前
【AI大数据工程师特训笔记】第04讲:PostgreSQL 数据库内置函数详解
大数据·数据库·笔记·ai·语言模型
苏渡苇11 小时前
Spring Cloud Alibaba:将 Sentinel 熔断限流规则持久化到 Nacos 配置中心
数据库·spring boot·mysql·spring cloud·nacos·sentinel·持久化