第二十六节:再探批处理框架[Zack.EFCore.Batch]、[EFCore.BulkExtensions]、[Z.EntityFramework.Plus.EFCore]
一. 批处理-[Zack.EFCore.Batch]
(一). 说明
1. 简介
(详细用法见GitHub:https://github.com/yangzhongke/Zack.EFCore.Batch) 【开源免费】
使用这个开发包, EFCore用户可以使用LINQ语句删除或者更新多条数据库记录,操作只执行一条SQL语句并且不需要首先把实体对象加载到内存中;通过该程序包基于SQLBulkCopy实现了批量插入。这个开发包支持 Entity Framework Core 5/6。
支持的数据库有:SQLServer、MySQL、Postgresql、Sqlite、Oracle、Dm(达梦)、InMemory(内存数据库)
2. 安装
下面以SQLServer+.EFCore6为例,其它DB的操作参考github
(1). EFCore必备程序集的安装,【Microsoft.EntityFrameworkCore】【Microsoft.EntityFrameworkCore.SqlServer】【Microsoft.EntityFrameworkCore.Tools】
(2). 安装【Zack.EFCore.Batch.MSSQL_NET6 6.0.15】,然后在OnConfiguring中添加 optionsBuilder.UseBatchEF_MSSQL();
(3). 在OnConfiguring中配置日志,用来输出翻译后的sql语句, 这里只能用系统默认的日志才能输出来
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//方式2:使用系统日志
optionsBuilder.LogTo(msg =>
{
if (msg.Contains("CommandExecuting")||msg.Contains("Sql"))
{
Console.WriteLine(msg);
}
});
optionsBuilder.UseSqlServer("Server=localhost;Database=EFCore6xDB;User ID=sa;Password=123456;");
optionsBuilder.UseBatchEF_MSSQL();// MSSQL Server 用户用这个
}
二. 各种用法
1. 批量插入
调用BulkInsertAsync方法进行批量插入,对于SQLServer内部采用的事输出日志无法捕获到翻译的sql.
代码分享
{
using var db = new EFCore6xDBContext();
List<UserInfo> userList = new List<UserInfo>();
for (int i = 1; i <= 40; i++)
{
UserInfo user = new UserInfo()
{
id = Guid.NewGuid().ToString("N"),
userName = "ypf1_" + i,
userPwd = "123456",
userAge = 19,
userGender = "男",
addTime = DateTime.Now,
delflag = 0
};
userList.Add(user);
}
await db.BulkInsertAsync(userList);
Console.WriteLine("新增成功");
}
2. 批量删除.
此处有两种写法都是调用DeleteRangeAsync方法,如下代码,二者翻译的SQL语句相同
代码分享:
{
using var db = new EFCore6xDBContext();
//写法1:
int count = await db.DeleteRangeAsync<UserInfo>(u => u.delflag == 1 && u.userName.Contains("ypf"));
//写法2
//int count = await db.Set<UserInfo>().Where(u => u.delflag == 1 && u.userName.Contains("ypf")).DeleteRangeAsync<UserInfo>(db);
Console.WriteLine($"删除成功,删除条数为{count}");
}
翻译的sql语句为:
写法1:
Delete FROM [UserInfo] WHERE [id] IN
(SELECT [id] FROM
(SELECT [u].[id], [u].[addTime], [u].[delflag], [u].[userAge], [u].[userGender], [u].[userName], [u].[userPwd]
FROM [UserInfo] AS [u]
WHERE ([u].[delflag] = 1) AND ([u].[userName] LIKE '%ypf%')) AS temp1 )
写法2:
Delete FROM [UserInfo] WHERE [id] IN
(SELECT [id] FROM
(SELECT [u].[id], [u].[addTime], [u].[delflag], [u].[userAge], [u].[userGender], [u].[userName], [u].[userPwd]
FROM [UserInfo] AS [u]
WHERE ([u].[delflag] = 1) AND ([u].[userName] LIKE '%ypf%')) AS temp1 )
3. 批量修改
调用BatchUpdate+Set+ExecuteAsync方法,支持基于原值修改,也支持直接改为全新的值
代码分享:
{
using var db = new EFCore6xDBContext();
int count = await db.BatchUpdate<UserInfo>()
.Set(u => u.userName, u => u.userName + "hh")
.Set(u => u.userGender, "女") //此处可以写成 "女", 也可以写成 u=>"女"
.Set(u => u.addTime, u => DateTime.Now) //此处必须写成 u=>DateTime.Now, 单独写一个 DateTime.Now报错
.Where(u => u.delflag == 1)
.ExecuteAsync();
Console.WriteLine($"修改成功,修改条数为{count}");
}
翻译的SQL语句为:
Update [UserInfo] SET [userName] = COALESCE([userName], '') + 'hh', [userGender] = N'女', [addTime] = GETDATE()
WHERE [id] IN(SELECT [id] FROM (SELECT [u].[id], [u].[addTime], [u].[delflag], [u].[userAge], [u].[userGender], [u].[userName], [u].[userPwd]
FROM [UserInfo] AS [u]
WHERE [u].[delflag] = 1) AS temp1 )
4. Take和Skip用法
和正常用法一样
代码分享:
{
using var db = new EFCore6xDBContext();
int count = await db.Set<UserInfo>().Where(u => u.delflag == 1 && u.userName.Contains("ypf"))
.Skip(1).Take(1)
.DeleteRangeAsync<UserInfo>(db);
Console.WriteLine($"删除成功,删除条数为{count}");
}
SQL语句
Delete FROM [UserInfo] WHERE [id] IN
(SELECT [id] FROM (SELECT [u].[id], [u].[addTime], [u].[delflag], [u].[userAge], [u].[userGender], [u].[userName], [u].[userPwd]
FROM [UserInfo] AS [u]
WHERE ([u].[delflag] = 1) AND ([u].[userName] LIKE '%ypf%')
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS FETCH NEXT @__p_0 ROWS ONLY) AS temp1 )
5. 事务内操作
经测试:批量插入不能放到事务里, 批量删除 和 批量修改可以放到事务里
代码分享:
查看代码
{
using var db = new EFCore6xDBContext();
using (var transaction = db.Database.BeginTransaction())
{
try
{
//1. 批量插入【不能放到事务里】
//List<UserInfo> userList = new List<UserInfo>();
//for (int i = 1; i <= 4; i++)
//{
// UserInfo user = new UserInfo()
// {
// id = Guid.NewGuid().ToString("N"),
// userName = "ypf1_" + i,
// userPwd = "123456",
// userAge = 19,
// userGender = "男",
// addTime = DateTime.Now,
// delflag = 0
// };
// userList.Add(user);
//}
//await db.BulkInsertAsync(userList);
//2-批量删除【可以放到事务里】
int count1 = await db.DeleteRangeAsync<UserInfo>(u => u.delflag == 0);
//3. 批量修改 【可以放到事务里】
int count2 = await db.BatchUpdate<UserInfo>()
.Set(u => u.userName, u => u.userName + "hh")
.Set(u => u.userGender, "女") //此处可以写成 "女", 也可以写成 u=>"女"
.Set(u => u.addTime, u => DateTime.Now) //此处必须写成 u=>DateTime.Now, 单独写一个 DateTime.Now报错
.Where(u => u.delflag == 0)
.ExecuteAsync();
//4. 模拟错误
UserInfo user2 = new UserInfo()
{
id = Guid.NewGuid().ToString("N") + "354545", //模拟错误
userName = "ypf2_",
userPwd = "123456",
userAge = 19,
userGender = "男",
addTime = DateTime.Now,
delflag = 0
};
db.Add(user2);
db.SaveChanges();
//最终事务提交
transaction.Commit();
Console.WriteLine("操作成功");
}
catch (Exception ex)
{
//using包裹不需要手写rollback
Console.WriteLine("错误为:" + ex.Message);
}
}
}
二. 批处理-[EFCore.BulkExtensions]
(一). 说明
1. 简介
详见:https://github.com/borisdj/EFCore.BulkExtensions 【开源免费】
该框架实现了实现了批量插入、更新、删除、读取等核心功能,支持SQLServer(2012+)、PostgreSQL、SQLite数据库(不支持MySQL),
(1).SQLServer 底层使用SqlBulkCopy进行插入,更新/删除将 BulkInsert 与原始 Sql MERGE结合。
(2).PostgreSQL 使用COPY BINARY 结合 ON CONFLICT进行更新(从 v6+ 开始支持)。
(3).对于SQLite,没有Copy工具,而是库使用结合了UPSERT的普通 SQL 。
PS:最新版支持MySQL了? 尚未测试,待补充
2. 安装
下面以SQLServer+.EFCore6为例
(1). 安装EFCore必备程序集,【Microsoft.EntityFrameworkCore】【Microsoft.EntityFrameworkCore.SqlServer】【Microsoft.EntityFrameworkCore.Tools】
(2). 安装程序集【EFCore.BulkExtensions6.5.1】
(3). 配置系统日志进行监测生成的SQL语句
注:如果使用 Windows 身份验证,则在 ConnectionString 中应该有Trusted_Connection=True; 因为需要 Sql 凭据才能保持连接,否则会报错【证书链是由不受信任的颁发机构颁发的】
【optionsBuilder.UseSqlServer("Server=localhost;Database=EFCore6xDB;User ID=sa;Password=123456;TrustServerCertificate=true");】
代码分享:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//方式2:使用系统日志
optionsBuilder.LogTo(msg =>
{
if (msg.Contains("CommandExecuting")||msg.Contains("Sql"))
{
Console.WriteLine(msg);
}
});
optionsBuilder.UseSqlServer("Server=localhost;Database=EFCore6xDB;User ID=sa;Password=123456;TrustServerCertificate=true");
}
(二). 各种用法
1. 批量插入
调用BulkInsertAsync方法,传入List集合即可实现批量插入, 无法捕获SQL语句
代码分享:
{
using var db = new EFCore6xDBContext();
List<UserInfo> userList = new List<UserInfo>();
for (int i = 0; i < 100; i++)
{
UserInfo user = new UserInfo()
{
id = Guid.NewGuid().ToString("N"),
userName = "ypf" + i,
userPwd = "123456",
userAge = 19,
userGender = "男",
addTime = DateTime.Now,
delflag = 0
};
userList.Add(user);
}
await db.BulkInsertAsync(userList);
Console.WriteLine("插入成功");
}
2. 批量删除.
写法1:调用BulkDelete方法,直接传入要删除的实体,必须包含主键值
写法2:配合where+BulkDelete方法,进行条件删除 【推荐_常用】
写法3:全部删除,支持原生truncate方法
代码分享:
{
using var db = new EFCore6xDBContext();
//写法1
//{
// List<UserInfo> userList = new List<UserInfo>();
// userList.Add(new UserInfo() { id = "00163a74ee27434286e6503a6c87eaeb" });
// userList.Add(new UserInfo() { id = "00c1839adaf94620af7f1418e013a1d0" });
// await db.BulkDeleteAsync(userList);
//}
//写法2
//{
// int count = await db.Set<UserInfo>().Where(u => u.delflag == 2 && u.userName.Contains("ypf")).BatchDeleteAsync();
// Console.WriteLine($"删除的条数为:{count}");
//}
//写法3-truncate全部删除
//{
// await db.TruncateAsync<UserInfo>();
//}
Console.WriteLine("删除成功");
}
写法2翻译的SQL语句:【直接翻译成了delete xxx where xxxx,性能高】
DELETE [u]
FROM [UserInfo] AS [u]
WHERE ([u].[delflag] = 2) AND ([u].[userName] LIKE '%ypf%')
写法3翻译的SQL语句为:
TRUNCATE TABLE [dbo].[UserInfo];
3. 批量修改
写法1:实现条件修改, where中查询条件,BatchUpdateAsync中使用表达式的形式可以基于原值的基础上进行修改,也可以直接修改为一个新的值
写法2:实现条件修改, where中查询条件,BatchUpdateAsync中传入一个简单实体,可以给他赋一个新值(注:这种写法不能基于原值修改)
写法3:更新的同时,赋默认值,通过传入一个新的参数来实现
代码分享:
{
using var db = new EFCore6xDBContext();
//写法1--Expression表达式
{
int count = await db.Set<UserInfo>().Where(u => u.delflag == 0).BatchUpdateAsync(u => new UserInfo
{
userAge = u.userAge + 2,
userName = u.userName + "_test",
userGender = "女"
});
Console.WriteLine(count);
}
//写法2-简单实体
//{
// int count = await db.Set<UserInfo>().Where(u => u.delflag == 0).BatchUpdateAsync(new UserInfo
// {
// userGender = "男男",
// userAge = 25
// });
// Console.WriteLine(count);
//}
//写法3--更新的同时,赋默认值
//{
// var updateCols = new List<string>() { nameof(UserInfo.userGender) }; //结果userGender字段会都被更新为Null
// int count = await db.UserInfo.Where(u => u.delflag == 0).BatchUpdateAsync(new UserInfo { userAge = 18 }, updateCols);
// Console.WriteLine(count);
//}
Console.WriteLine("更新成功");
}
写法1的SQL语句:
UPDATE u SET [u].[userAge] = [u].[userAge] + @param_0 , [u].[userName] = [u].[userName] + @param_1 , [u].[userGender] = @param_2
FROM [UserInfo] AS [u]
WHERE [u].[delflag] = 0
写法2的SQL语句:
UPDATE u SET [userAge] = @userAge, [userGender] = @userGender
FROM [UserInfo] AS [u]
WHERE [u].[delflag] = 0
写法3的SQL语句:
UPDATE u SET [userAge] = @userAge, [userGender] = @userGender
FROM [UserInfo] AS [u]
WHERE [u].[delflag] = 0
4. 手动修改配置
可以手动修改批量操作的配置,详见下图
代码分享:
{
using var db = new EFCore6xDBContext();
var bulkConfig = new BulkConfig { SetOutputIdentity = true, BatchSize = 4000 };
List<UserInfo> userList = new List<UserInfo>();
for (int i = 0; i < 10; i++)
{
UserInfo user = new UserInfo()
{
id = Guid.NewGuid().ToString("N"),
userName = "ypf" + i,
userPwd = "123456",
userAge = 19,
userGender = "男",
addTime = DateTime.Now,
delflag = 0
};
userList.Add(user);
}
db.BulkInsert(userList, bulkConfig);
}
5. 事务内操作
上述的批量增加、修改、删除,一条语句都是对应一个事务,直接生效,如果想让多条批量操作作为一个整体生效,需要用事务包裹。【特别注意:批量增加、删除、修改都可以放到事务里生效】
详见代码测试,模拟错误,上面的批量增加、修改、删除都没有成功
代码分享:
查看代码
{
using var db = new EFCore6xDBContext();
using (var transaction = db.Database.BeginTransaction())
{
try
{
//1. 批量插入【可以放到事务里】
List<UserInfo> userList = new List<UserInfo>();
for (int i = 1; i <= 4; i++)
{
UserInfo user = new UserInfo()
{
id = Guid.NewGuid().ToString("N"),
userName = "ypf1_" + i,
userPwd = "123456",
userAge = 19,
userGender = "男",
addTime = DateTime.Now,
delflag = 0
};
userList.Add(user);
}
await db.BulkInsertAsync(userList);
//2.批量删除【可以放到事务里】
await db.Set<UserInfo>().Where(u => u.delflag == 0 && u.userName.EndsWith("3")).BatchDeleteAsync();
//3.批量修改 【可以放到事务里】
await db.Set<UserInfo>().Where(u => u.delflag == 0).BatchUpdateAsync(u => new UserInfo
{
userAge = u.userAge + 2,
userName = u.userName + "_test",
userGender = "女"
});
//4. 模拟错误
UserInfo user2 = new UserInfo()
{
id = Guid.NewGuid().ToString("N") + "354545", //模拟错误
//id = Guid.NewGuid().ToString("N"),
userName = "ypf2_",
userPwd = "123456",
userAge = 19,
userGender = "男",
addTime = DateTime.Now,
delflag = 0
};
db.Add(user2);
db.SaveChanges();
//最终事务提交
transaction.Commit();
Console.WriteLine("操作成功");
}
catch (Exception ex)
{
//using包裹不需要手写rollback
Console.WriteLine("错误为:" + ex.Message);
}
}
}
6. 其它
(1). BulkSaveChanges
(2). BulkRead
(3). BulkInsertOrUpdate
(4). BulkInsertOrUpdateOrDelete
三. 批处理-[Z.EntityFramework.Plus.EFCore]
1. 简介
Z.EntityFramework.Plus.EFCore 是免费版本的,支持SQLServer和MySQL, 但是功能有限,仅支持批量删除和修改,其它操作需要使用收费版本
收费版本的程序集名称【Z.EntityFramework.Extensions.EFCore】,这个时候你会发现【Z.EntityFramework.Plus.EFCore】有需要依赖【Z.EntityFramework.Extensions.EFCore】
太坑了,我是不会用了, 动不动过期了,让下载最新版本,拜拜
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2018-06-19 ORM系列之Entity FrameWork详解