Skip to content

第三方批量操作库(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.2s1x
AddRange + SaveChanges38.5s1.2x
BulkInsertAsync0.8s56x ⚡

更新性能 ​

方法耗时相对速度
逐条更新52.3s1x
BulkUpdateAsync1.2s43x ⚡

删除性能 ​

方法耗时相对速度
逐条删除48.7s1x
BulkDeleteAsync0.9s54x ⚡

最佳实践 ​

✅ 推荐做法 ​

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.BulkExtensionsDapper原生 SaveChanges
性能⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
易用性⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
类型安全✅❌✅
学习曲线低中无
依赖EF CoreADO.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 倍提升

投资回报率: 极高!只需几行代码即可获得显著性能提升 🚀

基于 MIT 许可发布