Z.EntityFramework.Extensions.EFCore 批量更新(BulkUpdate)指定字段
Z.EntityFramework.Extensions.EFCore 批量更新(BulkUpdate)指定字段
一、Z.EntityFramework.Extensions.EFCore是什么?
Z.EntityFramework.Extensions.EFCore 是一个强大的第三方性能扩展库,专门为 Entity Framework Core 设计,用于弥补 EF Core 在批量操作方面的性能短板。
核心概念:解决 EF Core 的“痛点”
EF Core 是一个非常优秀的 ORM,它通过“变更跟踪器”来管理实体的状态。这在处理单个或少量实体时非常高效和方便。但是,当需要处理大量数据时,比如:
- 删除所有过期的日志记录
- 批量更新所有商品的价格
- 从一个表向另一个表导入成千上万条数据
如果使用原生的 EF Core,你可能会这样写:
// 批量删除 - 原生EF Core方式(低效)
var oldLogs = dbContext.Logs.Where(x => x.CreatedDate < DateTime.Now.AddYears(-1));
dbContext.Logs.RemoveRange(oldLogs);
await dbContext.SaveChangesAsync();// 批量更新 - 原生EF Core方式(低效)
var products = dbContext.Products.Where(x => x.CategoryId == 1);
foreach (var product in products)
{product.Price *= 1.1m; // 涨价10%
}
await dbContext.SaveChangesAsync();
这种方式的问题在于:
- 删除:它会先将所有符合条件的数据从数据库查询出来,加载到内存中,并开始跟踪它们。然后标记为
Deleted,最后执行SaveChanges时,EF Core 会生成 N 条 独立的DELETESQL 语句(N是被删除的记录数)。 - 更新:同样,它先查询出所有数据,在内存中逐个修改,最后生成 N 条 独立的
UPDATESQL 语句。
当 N 很大时(比如10万条),这个过程会消耗大量内存和网络资源,速度极慢,甚至可能导致应用程序崩溃。
Z.EntityFramework.Extensions.EFCore 的解决方案
这个库引入了批量操作的概念,它不通过 EF Core 的变更跟踪器,而是直接生成并执行一条高效的 SQL 语句。
主要功能:
-
BulkDelete- 原理:根据你提供的条件(如
Where语句),直接在数据库端生成并执行一条DELETE ... WHERE ...的 SQL。 - 效果:无论删除多少条数据,都只在数据库端执行一次往返。
// 使用 Z.EntityFramework.Extensions.EFCore 方式(高效) await dbContext.Logs.Where(x => x.CreatedDate < DateTime.Now.AddYears(-1)).BatchDeleteAsync();生成的SQL:
DELETE FROM [Logs] WHERE [CreatedDate] < '2023-01-01' - 原理:根据你提供的条件(如
-
BulkUpdate- 原理:你提供一个需要更新的数据源(可以是查询结果,也可以是一个内存列表)和更新的规则,它生成一条
UPDATE ... SET ... WHERE ...的 SQL。 - 效果:一次数据库往返完成所有更新。
// 使用 Z.EntityFramework.Extensions.EFCore 方式(高效) await dbContext.Products.Where(x => x.CategoryId == 1).BatchUpdateAsync(new Product { Price = Product.Price * 1.1m });生成的SQL:
UPDATE [Products] SET [Price] = [Price] * 1.1 WHERE [CategoryId] = 1 - 原理:你提供一个需要更新的数据源(可以是查询结果,也可以是一个内存列表)和更新的规则,它生成一条
-
BulkInsert- 原理:将一个巨大的
List<T>一次性插入数据库。相比于 EF Core 原生的AddRangeAsync+SaveChangesAsync(会生成大量INSERT语句),它使用数据库原生的批量插入机制(如 SqlBulkCopy for SQL Server),速度极快。 - 效果:性能比原生方式提升几个数量级。
var massiveList = new List<Product>(); // 假设这里有10万个产品 // ... 填充数据 await dbContext.BulkInsertAsync(massiveList); - 原理:将一个巨大的
-
BulkMerge(Upsert)- 这是一个更高级的功能,相当于
INSERT ... ON DUPLICATE KEY UPDATE(MySQL)或MERGE(SQL Server)。如果记录存在则更新,不存在则插入。这在数据同步场景中非常有用。
- 这是一个更高级的功能,相当于
优点
- 极高的性能提升:在处理大量数据时,性能提升可达数十倍甚至数百倍。
- 低内存消耗:避免了将大量数据加载到内存中。
- 减少数据库压力:将多次往返减少为一次,并生成更高效的 SQL。
- 易于使用:API 设计流畅,与 LINQ 无缝集成。
缺点与注意事项
- 第三方库:这不是微软官方的包,需要信任并依赖第三方开发者。
- 绕过变更跟踪器:因为它直接操作数据库,所以不会触发 EF Core 的变更跟踪器、
SaveChanges拦截器以及数据库的并发检查。如果你的业务逻辑严重依赖这些机制,需要谨慎使用。 - 许可和成本:该库在早期版本通常是免费的,但对于新版本或高级功能,可能需要购买商业许可证。请务必查看其官方许可条款。
二、Z.EntityFramework.Extensions.EFCore 中的 BulkUpdate 方法支持更新指定字段
在更新数据库时指定字段(而不是更新所有字段)是一个非常常见的做法,在性能方便带来的巨大的优势。Z.EntityFramework.Extensions.EFCore 中的 BulkUpdate 方法支持更新指定字段。但不同版本的实现方式可能有所不同。 由于本人的版本是:Z.EntityFramework.Extensions.EFCore 5.17.2.0,这里我只介绍 Z.EntityFramework.Extensions.EFCore 5.17.2.0 支持的方式。
使用 ColumnInputExpression 指定更新字段
这是最直接和常用的方法,明确指定要更新的属性:
// 只更新 Name 和 Email 字段
dbContext.BulkUpdate<Customer>(customers, options => {options.ColumnInputExpression = m => new { m.Name, m.Email }; // 使用 lambda 表达式
});
如果你想要排除某些字段:
dbContext.BulkUpdate<Customer>(customers, options => {// 使用排除方式 - 先包含所有字段,然后排除不需要的options.ColumnInputExpression = m => m;options.IgnoreOnUpdateExpression = m => new { m.CreatedDate, m.IsDeleted };
});
