背景

EntityFramework Core有许多新的特性,其中一个重要特性是 批量操作。

批量操作意味着不需要为每次Insert/Update/Delete操作发送单独的命令,而是在一次SQL请求中发送批量组合指令。

EFCore批量操作实践

批处理是期待已久的功能,社区多次提出要求。现在EFCore支持开箱即用确实很棒,可以提高应用程序的性能和速度。

P1 对比实践

下面以常见的批量插入为例,使用SQL Server Profiler 观察实际产生并执行的SQL语句。

另一种观察EFCore生成sql的方法:

添加Nlog支持,关注Microsoft.EntityFrameworkCore.Database.Command 日志

<logger name="Microsoft.EntityFrameworkCore.Database.Command" minlevel="Debug" writeTo="sql" />

定义插入模型Category, 插入4个实体,这里为什么强调4,请留意下文。

    public class Category
{
public int Id { get; set; }
public int CategoryID { get; set; }
public string CategoryName { get; set; }
} /*EFCore 查看模型属性,有Id使用id作为主键,
没有Id,搜索public "{TableName}Id"作为主键,默认为int形主键设置标记列自增;
*/ info: Microsoft.EntityFrameworkCore.Database.Command[]
Executing DbCommand [Parameters=[], CommandType='Text', CommandTimeout='']
CREATE TABLE [Categories] (
[Id] int NOT NULL IDENTITY,
[CategoryID] int NOT NULL,
[CategoryName] nvarchar(max) NULL,
CONSTRAINT [PK_Categories] PRIMARY KEY ([Id])
);

using (var db = new BloggingContext())
{
  db.Categories.Add(new Category() { CategoryID = 1, CategoryName = "Clothing" });
  db.Categories.Add(new Category() { CategoryID = 2, CategoryName = "Footwear" });
  db.Categories.Add(new Category() { CategoryID = 3, CategoryName = "Accessories" });
  db.Categories.Add(new Category() { CategoryID = 4, CategoryName = "Accessories" });
  db.SaveChanges();
} 

当执行SaveChanges(), 日志显示:

info: Microsoft.EntityFrameworkCore.Database.Command[]
Executing DbCommand [Parameters=[@p0='', @p1='Clothing' (Size = ), @p2='', @p3='Footwear' (Size = ), @p4='', @p5='Accessories' (Size = ), @p6='', @p7='Accessories' (Size = )], CommandType='Text', CommandTimeout='']
SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]);
MERGE [Categories] USING (
VALUES (@p0, @p1, ),
(@p2, @p3, ),
(@p4, @p5, ),
(@p6, @p7, )) AS i ([CategoryID], [CategoryName], _Position) ON =
WHEN NOT MATCHED THEN
INSERT ([CategoryID], [CategoryName])
VALUES (i.[CategoryID], i.[CategoryName])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0; SELECT [t].[Id] FROM [Categories] t
INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])
ORDER BY [i].[_Position];

从SQL Profiler追溯到的SQL:

exec sp_executesql N'SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]);
MERGE [Categories] USING (
VALUES (@p0, @p1, 0),
(@p2, @p3, 1),
(@p4, @p5, 2),
(@p6, @p7, 3)) AS i ([CategoryID], [CategoryName], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([CategoryID], [CategoryName])
VALUES (i.[CategoryID], i.[CategoryName])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0;

SELECT [t].[Id] FROM [Categories] t
INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])
ORDER BY [i].[_Position];

',N'@p0 int,@p1 nvarchar(4000),@p2 int,@p3 nvarchar(4000),@p4 int,@p5 nvarchar(4000),@p6 int,@p7 nvarchar(4000)',@p0=1,@p1=N'Clothing',@p2=2,@p3=N'Footwear',@p4=3,@p5=N'Accessories',@p6=4,@p7=N'Accessories'

如你所见,批量插入没有产生4个独立的语句,而是被组合为一个传参存储过程脚本(用列值作为参数);

如果使用EF6执行相同的代码,则在SQL Server Profiler中将看到4个独立的插入语句 。

① 就性能和速度而言,EFCore批量插入更具优势。

② 若数据库是针对云部署,EF6运行这些查询,还将产生额外的流量成本。

经过验证:EFCore批量更新、批量删除功能,EFCore均发出了使用sp_executesql存储过程+批量参数构建的SQL脚本。

P2  sp_executesql ?

起关键作用的 sp_executesql存储过程: 可以多次执行的语句或批处理 (可带参)

-- Syntax for SQL Server, Azure SQL Database, Azure SQL Data Warehouse, Parallel Data Warehouse  

sp_executesql [ @stmt = ] statement
[
{ , [ @params = ] N'@parameter_name data_type [ OUT | OUTPUT ][ ,...n ]' }
{ , [ @param1 = ] 'value1' [ ,...n ] }
]

注意官方限制:

The amount of data that can be passed by using this method is limited by the number of parameters allowed. SQL Server procedures can have, at most, 2100 parameters. Server-side logic is required to assemble these individual values into a table variable or a temporary table for processing.       // SQL存储过程最多可使用2100个参数

P3 豁然开朗

SqlServer  sp_executesql存储过程最多支持2100个批量操作形成的列值参数,所以遇到很大数量的批量操作,EFCore SqlProvider会帮我们将批量操作分块传输

实际上EFCore 对于少于4个的批量命令,不会使用sp_executesql 存储过程,我这边自己根据官方验证确实如此:

https://github.com/aspnet/EntityFrameworkCore/pull/10091

https://github.com/aspnet/EntityFrameworkCore/blob/v3.1.0-preview3.19554.8/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs

估摸着EFCore使用sp_executesql 也是有点耗资源的,对于小批量(小于4条的批量操作)依旧是产生单条sql。

// 同时EFCore开放了【配置关系型数据库批量操作大小】
protected override void OnConfiguring(DbContextOptionsBuilder optionbuilder)
{
string sConnString = @"Server=localhost;Database=EFSampleDB;Trusted_Connection=true;";
optionbuilder.UseSqlServer(sConnString , b => b.MaxBatchSize(1)); // 批量操作的SQL语句数量,也可设定为1禁用批量插入
}

总结

① EFCore 相比EF6,已经支持批量操作,能有效提高应用程序的性能

② EFCore的批量操作能力,由对应的DataBaseProvider支撑(Provider实现过程跟背后的存储载体密切相关)

-  对于小批量操作(当前EFCore默认MinBatchSize为4》),EFCore不会启用sp_executesql

 - 大批量操作会使用存储过程sp_executesql ,存储过程的列值参数最多2100 个,这个关键因素决定了在大批量操作的时候 依旧会被分块传输。

③ 另外一个批量操作的方法,这里也点一下:构造Rawsql【EFCore支持Rawsql】。

  sqlite不支持存储过程,为完成批量插入,可采用此方案。

var insertStr = new StringBuilder();
insertStr.AppendLine("insert into ProfileUsageCounters (profileid,datetime,quota,usage,natureusage) values");
var txt = insertStr.AppendLine(string.Join(',', usgaeEntities.ToList().Select(x =>
{
return $"({x.ProfileId},{x.DateTime},{x.Quota},{x.Usage},{x.NatureUsage})";
}).ToArray()));
await _context.Database.ExecuteSqlCommandAsync(txt.ToString());

+ https://github.com/aspnet/EntityFrameworkCore/issues/6604

+ https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters?redirectedfrom=MSDN

EFCore批量操作,你真的清楚吗的更多相关文章

  1. 【在Windows下进行的编程人员,你真的需要学习下Linux】

    从业几年,发现好多编程人员,严重依赖UI工具.对一些命令操作,十分的反感.尤其是从事Windows系统中的程序开发的人员.由于微软对开发工具,编程套件的极限优化.开发出更多的 面向UI的开发工具.从V ...

  2. EFCore Owned Entity Types,彩蛋乎?鸡肋乎?之彩蛋篇

    EFCore Owned Entity Types的定义 EFCore Owned Entity Types的文档在这里:https://docs.microsoft.com/zh-cn/ef/cor ...

  3. EFCore Lazy Loading + Inheritance = 干净的数据表 (二) 【献给处女座的DB First程序猿】

    前言 本篇是上一篇EFCore Lazy Loading + Inheritance = 干净的数据表 (一) [献给处女座的DB First程序猿] 前菜 的续篇.这一篇才是真的为处女座的DB Fi ...

  4. EFCore Lazy Loading + Inheritance = 干净的数据表 (一) 【献给处女座的DB First程序猿】

    前言 α角 与 β角 关于α角 与 β角的介绍,请见上文 如何用EFCore Lazy Loading实现Entity Split. 本篇会继续有关于β角的彩蛋在等着大家去发掘./斜眼笑 其他 本篇的 ...

  5. 如何用EFCore Lazy Loading实现Entity Split

    α角 与 β角 支持 现实生活 的 计算机系统,总有着两大偏差,第一个是 现实生活 与 计算机系统 的α角,另外一个是计算机系统的 逻辑设计 与 物理设计 的β角.举个栗子: α角:假设某个公司的商业 ...

  6. 如何从40亿整数中找到不存在的一个 webservice Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库 WPF实战案例-打印 RabbitMQ与.net core(五) topic类型 与 headers类型 的Exchange

    如何从40亿整数中找到不存在的一个 前言 给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数.(在文件中至少确实一个这样的数-为什么?).在具有足够内存的情况 ...

  7. NLP度量指标BELU真的完美么?

    摘要: NLP重要评价准则之一——BLEU,真的完美无缺么? 刚接触自然语言处理的朋友通常会问我:当系统的输出是文本,而非对输入文本进行某种分类,如何对该系统进行评估.当模型的输入是文本信息,输出也是 ...

  8. .netCore+Vue 搭建的简捷开发框架 (2)--仓储层实现和EFCore 的使用

    书接上文,继续搭建我们基于.netCore 的开发框架.首先是我们的项目分层结构. 这个分层结构,是参考张老师的分层结构,但是实际项目中,我没有去实现仓储模型.因为我使用的是EFCore ,最近也一直 ...

  9. EF Core扩展工具记录 批量操作 记录修改删除历史 动态linq

      Microsoft.EntityFrameworkCore.UnitOfWork  Microsoft.EntityFrameworkCore的插件,用于支持存储库,工作单元模式以及支持分布式事务 ...

随机推荐

  1. libevent::事件

    /***************************************************************** 函数功能: 创建事件集 ********************* ...

  2. StringBuffer类讲解

    package Main; import java.util.Scanner; public class Main { public static void main(String[] args) { ...

  3. 百万年薪python之路 -- 文件操作练习

    1.有如下文件,a1.txt,里面的内容为: 老男孩是最好的学校, 全心全意为学生服务, 只为学生未来,不为牟利. 我说的都是真的.哈哈 分别完成以下的功能: a,将原文件全部读出来并打印. with ...

  4. C# .NET .NET Framework .NET CORE 等的关系简介

    2019新的一年,祝大家新年快乐,工作生活一帆风顺,心想事成!诸事大吉! 这篇文章是我今年的第一篇博客,主题是:C#  .NET  .NET Framework   .NET CORE  等这些名词之 ...

  5. SpringBoot整合MybatisPlus3.X之自定义Mapper(十)

    pom.xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId& ...

  6. 【spock】单测竟然可以如此丝滑

    0. 为什么人人都讨厌写单测 在之前的关于swagger文章里提到过,程序员最讨厌的两件事,一件是别人不写文档,另一件就是自己写文档.这里如果把文档换成单元测试也同样成立. 每个开发人员都明白单元测试 ...

  7. 《大数据实时计算引擎 Flink 实战与性能优化》新专栏

    基于 Flink 1.9 讲解的专栏,涉及入门.概念.原理.实战.性能调优.系统案例的讲解. 专栏介绍 扫码下面专栏二维码可以订阅该专栏 首发地址:http://www.54tianzhisheng. ...

  8. spark java api数据分析实战

    1 spark关键包 <!--spark--> <dependency> <groupId>fakepath</groupId> <artifac ...

  9. 正则表达式和python中的re模块

    ---恢复内容开始--- 常用的正则匹配规则 元字符 量词 字符组 字符集 转义符 贪婪匹配 re模块使用正则表达式 实例引入(是否使用re模块和正则表达式的区别) # 不使用正则表达式 phone_ ...

  10. TypeError: expected string or bytes-like object

    在写Python代码的时候,遇到了"TypeError: a bytes-like object is required, not 'str'"错误,此处实验机器的Python环境 ...