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 小时前
MySQL JSON_SET() 函数
数据库·mysql·json
山峰哥6 小时前
数据库工程与SQL调优——从索引策略到查询优化的深度实践
数据库·sql·性能优化·编辑器
较劲男子汉6 小时前
CANN Runtime零拷贝传输技术源码实战 彻底打通Host与Device的数据传输壁垒
运维·服务器·数据库·cann
java搬砖工-苤-初心不变6 小时前
MySQL 主从复制配置完全指南:从原理到实践
数据库·mysql
山岚的运维笔记8 小时前
SQL Server笔记 -- 第18章:Views
数据库·笔记·sql·microsoft·sqlserver
roman_日积跬步-终至千里9 小时前
【LangGraph4j】LangGraph4j 核心概念与图编排原理
java·服务器·数据库
汇智信科9 小时前
打破信息孤岛,重构企业效率:汇智信科企业信息系统一体化运营平台
数据库·重构
野犬寒鸦9 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
晚霞的不甘10 小时前
揭秘 CANN 内存管理:如何让大模型在小设备上“轻装上阵”?
前端·数据库·经验分享·flutter·3d
市场部需要一个软件开发岗位11 小时前
JAVA开发常见安全问题:纵向越权
java·数据库·安全