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;

    }
相关推荐
倔强的石头_3 小时前
《Kingbase护城河》——数据库存储空间全景探测与精细化瘦身实战
数据库
冬奇Lab16 小时前
每日一个开源项目(第134篇):Zvec - 阿里开源的嵌入式向量数据库,向量搜索界的 SQLite
数据库·人工智能·llm
ClouGence1 天前
Oracle CDC 架构优化:从主库直连到 DataGuard 备库同步
数据库·后端·oracle
无响应de神1 天前
三、用户与权限管理
数据库·mysql
麦聪聊数据2 天前
数据服务化时代:企业数据能力输出的核心路径
数据库
shushangyun_2 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
DARLING Zero two♡2 天前
【MySQL数据库】数据类型与表约束
数据库·mysql
曹牧2 天前
Oracle EXPLAIN PLAN
数据库·oracle
BD_Marathon2 天前
SQL学习指南——视图
数据库·sql
活宝小娜2 天前
mysql详细安装教程
数据库·mysql·adb