介绍
在开发企业级应用或数据密集型系统时,我们经常会遇到大批量数据的插入、更新或删除操作。使用 Entity Framework Core(简称 EF Core)默认的方式处理这类操作时,性能往往会成为瓶颈。平时大多时候都是简单的CURD,但数据量较大时就不能平时处理方法了。
平时用用ABP框架,对数据的CURD都非常简单,直接使用框架提供的Repository即可。例如插入多条数据时我们会这样写:
await Repository.InsertManyAsync(list);
如果遇到上万数据基本就用不了。因为:
- 默认 InsertManyAsync 是逐条插入;
- 插入时每条记录可能会触发审计日志、领域事件、验证等;
- EF Core 的 SaveChangesAsync 在批量操作中并不高效。
.net对于上万以上的数据操作可以使用EFCore.BulkExtensions 。
EFCore.BulkExtensions
Github地址:➤ https://github.com/abpframework/abp
EFCore.BulkExtensions 能够解决的问题:
- 使用 SqlBulkCopy 等底层机制,批量执行数据库操作
- 避免 ChangeTracker,提升操作效率
- 极大减少数据库连接的往返次数
EFCore.BulkExtensions 机制:
- 插入(Insert):使用 SqlBulkCopy
- 更新/删除/合并:使用 MERGE SQL 语法
- 避免 EF Core 的 ChangeTracker
- 可配置批次大小、事务处理、ID输出等选项
这使得它相比 EF Core 原生操作,执行效率提升数倍至数百倍。
提供的主要方法
BulkInsertAsync批量插入数据
BulkUpdateAsync批量更新数据
BulkDeleteAsync批量删除数据
BulkMergeAsync插入或更新(UPSERT)
BulkReadAsync从数据库中批量读取数据
使用实例
在开发过程中系统有一个数据导入功能,数据都百万条以上,因为系统是第一版,就先考虑将功能实现,并未考虑分表和分库的做法,就全部存到一个数据。先导入17万条数数据,但在下面插入代码的地方就动。
await Repository.InsertManyAsync(list);
改用EFCore.BulkExtensions进行批量操作。
使用安装命令进行安装或者nuget包管理器中输入EFCore.BulkExtensions进行安装。
dotnet add package EFCore.BulkExtensions
值得注意的是这里,需要安装.net 对应版本,例如我使用.net8 安装的版本就需要是8.x的。
安装好就可以使用了,这里使用新增和更新来做示例。
public async Task BatchInsertAsync(List<Poi> entities)
{
var dbContext = await GetDbContextAsync();
await dbContext.BulkInsertAsync(entities, new BulkConfig
{
BatchSize = 5000, // 批量插入的大小
SetOutputIdentity = false // 如果不需要返回自增ID
});
}
public async Task BatchUpdateAsync(List<Poi> entities)
{
var dbContext = await GetDbContextAsync();
await dbContext.BulkUpdateAsync(entities, new BulkConfig
{
SetOutputIdentity = false,
UseTempDB = true, // 使用临时表提高性能
PreserveInsertOrder = false, // 不保留插入顺序
EnableStreaming = true, // 启用流式处理
TrackingEntities = false, // 不跟踪实体变化
BulkCopyTimeout = 0, // 无超时限制
SqlBulkCopyOptions = SqlBulkCopyOptions.Default | SqlBulkCopyOptions.TableLock
});
}
下面是代码运行后的效果:
18万(接近18万)条数据插入数据消耗时间11734 毫秒 (11.734 秒)
200万(接近200万)条数据116523 毫秒(1 分 56 秒)
200万条数据测试更新时不能直接写,会超时,虽然已经设置了不限制超时时间,但还是会提超时。
await _testRepository.BatchUpdateAsync(batch);
使用分批次更新,则可以继续执行
// 记录时间
var stopwatch = new Stopwatch();
stopwatch.Start();
const int batchSize = 10000;
foreach (var batch in updateEntities.Chunk(batchSize))
{
await _testRepository.BatchUpdateAsync(batch.ToList());
}
stopwatch.Stop();
Console.WriteLine($"插入和更新完成,用时:{stopwatch.Elapsed.TotalSeconds}秒");
200万数据更新399940 毫秒 ( 6 分 39 秒)
测试结果和官网有一定差异,我使用的表是真实项目表,表中有40多个字段,字段内容数据较多,且电脑性能不一样。
一般框架都是为了处理通用业务,封装的CRUD也是针对通用业务的,对于大数据处理还是需要自己写一下的。EFCore.BulkExtensions 是 EF Core 在处理高性能数据写入时的重要补充。它弥补了 EF Core 在大批量数据处理方面的不足,为系统提供了数倍甚至数百倍的性能提升。