Appearance
第三方批量操作库(EFCore.BulkExtensions)
概述
EF Core 原生的 SaveChanges() 在大批量数据操作时性能较差。EFCore.BulkExtensions 是一个高性能扩展库,通过直接使用数据库原生批量操作 API,可提升 10-50 倍性能。
GitHub: https://github.com/zzzprojects/EntityFramework-Extensions
NuGet: EFCore.BulkExtensions
安装
bash
dotnet add package EFCore.BulkExtensions支持数据库:
- ✅ SQL Server (使用 SqlBulkCopy)
- ✅ PostgreSQL (使用 COPY)
- ✅ MySQL (使用 LOAD DATA)
- ✅ SQLite (使用 INSERT OR REPLACE)
核心功能
1. 批量插入
csharp
// ❌ 原生方式 - 慢
var products = GenerateProducts(10000);
context.Products.AddRange(products);
await context.SaveChangesAsync(); // ~10秒
// ✅ 批量插入 - 快
await context.BulkInsertAsync(products); // ~0.2秒 (50倍提升!)高级选项:
csharp
await context.BulkInsertAsync(products, new BulkConfig
{
BatchSize = 2000, // 批次大小
NotifyAfter = 500, // 每处理500条通知一次
CalculateStats = true, // 计算统计信息
SetOutputIdentity = true, // 回填自增ID
PreserveInsertOrder = true // 保持插入顺序
});
// 查看统计
Console.WriteLine($"Inserted: {stats.StatsInfo.InsertedCount}");2. 批量更新
csharp
// 批量更新所有产品价格上涨10%
var products = await context.Products.ToListAsync();
products.ForEach(p => p.Price *= 1.1m);
await context.BulkUpdateAsync(products);条件更新:
csharp
await context.BulkUpdateAsync(
products,
new BulkConfig
{
UpdateByProperties = new List<string> { "Id" }, // 匹配键
PropertiesToInclude = new List<string> { "Price", "Stock" } // 只更新指定字段
});3. 批量删除
csharp
// 批量删除停产产品
var discontinuedProducts = await context.Products
.Where(p => p.IsDiscontinued)
.ToListAsync();
await context.BulkDeleteAsync(discontinuedProducts);4. 批量插入或更新(Upsert)
csharp
// 如果存在则更新,不存在则插入
await context.BulkInsertOrUpdateAsync(products, new BulkConfig
{
UpdateByProperties = new List<string> { "Sku" } // 根据SKU判断
});5. 读取大量数据
csharp
// 高效读取百万级数据
var config = new BulkConfig
{
BatchSize = 5000,
TrackingEntities = false // 不跟踪,提升性能
};
var products = new List<Product>();
await context.BulkReadAsync(products, config);实际应用场景
场景 1: 数据导入
csharp
public class DataImportService
{
private readonly AppDbContext _context;
public async Task ImportProductsFromCsv(string filePath)
{
var products = ParseCsvFile(filePath); // 解析CSV
// 验证数据
var validProducts = products.Where(p => ValidateProduct(p)).ToList();
// 批量插入
await _context.BulkInsertAsync(validProducts, new BulkConfig
{
BatchSize = 2000,
NotifyAfter = 1000,
OnProgress = progress =>
{
Console.WriteLine($"Imported: {progress}%");
}
});
}
}场景 2: 定时同步
csharp
public class DataSyncService
{
public async Task SyncFromExternalApi()
{
// 从外部API获取数据
var externalData = await _apiClient.GetProductsAsync();
// 转换为实体
var products = externalData.Select(MapToProduct).ToList();
// Upsert: 存在则更新,不存在则插入
await _context.BulkInsertOrUpdateAsync(products, new BulkConfig
{
UpdateByProperties = new List<string> { "ExternalId" },
PropertiesToInclude = new List<string>
{
"ExternalId", "Name", "Price", "Stock"
}
});
}
}场景 3: 历史数据归档
csharp
public class ArchiveService
{
public async Task ArchiveOldOrders()
{
var cutoffDate = DateTime.UtcNow.AddYears(-2);
// 读取旧订单
var oldOrders = await _context.Orders
.Where(o => o.OrderDate < cutoffDate)
.ToListAsync();
// 写入归档表
await _archiveContext.BulkInsertAsync(oldOrders.Select(o => new OrderArchive
{
Id = o.Id,
OrderDate = o.OrderDate,
TotalAmount = o.TotalAmount
}));
// 删除原表数据
await _context.BulkDeleteAsync(oldOrders);
}
}性能对比
测试环境
- 数据量: 100,000 条记录
- 数据库: SQL Server 2019
- 硬件: 8核 CPU, 16GB RAM, SSD
插入性能
| 方法 | 耗时 | 相对速度 |
|---|---|---|
| SaveChanges() | 45.2s | 1x |
| AddRange + SaveChanges | 38.5s | 1.2x |
| BulkInsertAsync | 0.8s | 56x ⚡ |
更新性能
| 方法 | 耗时 | 相对速度 |
|---|---|---|
| 逐条更新 | 52.3s | 1x |
| BulkUpdateAsync | 1.2s | 43x ⚡ |
删除性能
| 方法 | 耗时 | 相对速度 |
|---|---|---|
| 逐条删除 | 48.7s | 1x |
| BulkDeleteAsync | 0.9s | 54x ⚡ |
最佳实践
✅ 推荐做法
1. 合理设置批次大小
csharp
await context.BulkInsertAsync(largeList, new BulkConfig
{
BatchSize = 2000, // 小批次,避免内存溢出
BatchTimeout = 30 // 超时时间(秒)
});建议:
- 小数据(< 1000): BatchSize = 1000
- 中数据(1000-10000): BatchSize = 2000
- 大数据(> 10000): BatchSize = 5000
2. 禁用变更跟踪
csharp
await context.BulkInsertAsync(products, new BulkConfig
{
TrackingEntities = false // 不需要跟踪
});3. 监控进度
csharp
await context.BulkInsertAsync(hugeList, new BulkConfig
{
NotifyAfter = 1000,
OnProgress = progress =>
{
_logger.LogInformation($"Progress: {progress}%");
}
});4. 事务处理
csharp
using var transaction = await context.Database.BeginTransactionAsync();
try
{
await context.BulkInsertAsync(orders);
await context.BulkUpdateAsync(inventory);
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}❌ 避免的陷阱
1. 不要在小数据量时使用
csharp
// ❌ 不必要
if (products.Count < 100)
{
await context.BulkInsertAsync(products); // 开销大于收益
}
// ✅ 直接保存
context.Products.AddRange(products);
await context.SaveChangesAsync();阈值: < 500 条记录,使用原生 SaveChanges 更简单
2. 不要忘记索引重建
csharp
// 大批量插入后,重建索引
await context.BulkInsertAsync(millionRecords);
// SQL Server 自动处理,但其他数据库可能需要手动重建索引3. 注意内存使用
csharp
// ❌ 一次性加载百万数据到内存
var allProducts = await context.Products.ToListAsync(); // OOM!
// ✅ 分批处理
const int batchSize = 10000;
for (int i = 0; i < totalCount; i += batchSize)
{
var batch = await context.Products
.Skip(i)
.Take(batchSize)
.ToListAsync();
await ProcessBatchAsync(batch);
}高级配置
完整配置示例
csharp
var bulkConfig = new BulkConfig
{
// 批次控制
BatchSize = 2000,
BatchTimeout = 30,
// 进度监控
NotifyAfter = 500,
OnProgress = progress => Console.WriteLine($"{progress}%"),
// 字段控制
PropertiesToInclude = new List<string> { "Id", "Name", "Price" },
PropertiesToExclude = new List<string> { "CreatedAt", "UpdatedAt" },
// 匹配规则
UpdateByProperties = new List<string> { "Sku" },
// 身份管理
SetOutputIdentity = true,
PreserveInsertOrder = true,
// 跟踪
TrackingEntities = false,
// 统计
CalculateStats = true,
// SQL Server 特定
UseTempDB = true, // 使用临时表
// 事件
OnBeforeWrite = entities => Console.WriteLine("Starting bulk write..."),
OnAfterWrite = entities => Console.WriteLine("Bulk write completed!")
};
await context.BulkInsertAsync(entities, bulkConfig);与其他库对比
| 特性 | EFCore.BulkExtensions | Dapper | 原生 SaveChanges |
|---|---|---|---|
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 易用性 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 类型安全 | ✅ | ❌ | ✅ |
| 学习曲线 | 低 | 中 | 无 |
| 依赖 | EF Core | ADO.NET | 无 |
| 适用场景 | 批量操作 | 微优化 | 常规CRUD |
常见问题
Q1: 是否支持事务?
A: ✅ 完全支持,建议在事务中执行批量操作
Q2: 如何处理并发冲突?
A: 批量操作不使用乐观并发控制,如需并发控制,使用原生 SaveChanges
Q3: 支持哪些数据库版本?
A:
- SQL Server 2008+
- PostgreSQL 9.5+
- MySQL 5.7+
- SQLite 3.0+
总结
何时使用 EFCore.BulkExtensions?
✅ 推荐使用:
- 批量插入 > 1000 条记录
- 批量更新/删除大量数据
- 数据导入/导出
- ETL 处理
- 数据迁移
❌ 不推荐:
- 小数据量(< 500 条)
- 需要复杂业务逻辑验证
- 需要领域事件触发
- 单条记录的 CRUD
性能提升预期
- 插入: 30-50 倍提升
- 更新: 20-40 倍提升
- 删除: 30-50 倍提升
投资回报率: 极高!只需几行代码即可获得显著性能提升 🚀