Entity Framework批量插入非常缓慢

3
我正在使用EF 6。我试图在每插入100个实体后将更改保存到数据库,共插入约200,000个实体。
问题是,保存50,000个实体花费了11个小时,而且仍然很慢。我正在使用WebJobs运行此操作,并且作业发布在与主网站相同的Azure Web应用程序上。这是因为WebJob没有足够的资源,还是因为每插入100个实体后保存的方式不正确,或者是方法本身的问题?
public void SaveLeadsForBuyer(ISenderModel model)
{
    var rowCounter = 0;

    foreach (var deliveryRecord in model.Customers.Select(customerModel => new DeliveryRecord()
    {
        BuyerId = model.Buyer.Id,
        AspNetUserId = customerModel.Id,
        DeliveryType = model.Buyer.DeliveryType,
        CreatedOn = DateTime.UtcNow
    }))
    {
        ++rowCounter;

        _unit.Repository<DeliveryRecord>().Insert(deliveryRecord);

        _unit.SaveChangesPartially(rowCounter, 100);
    }

    _unit.SaveChanges();
}

助手

public static class UnitOfWorkHelper
{
    /// <summary>
    /// Helper method triggers SaveChanges() after amount of rows provided through "amount" parameter in method
    /// </summary>
    /// <param name="unit">UnitOfWork object</param>
    /// <param name="count">Current amount of rows</param>
    /// <param name="saveCount">Amount when to save changes to database</param>
    public static void SaveChangesPartially(this IUnitOfWorkAsync unit, int count, int saveCount)
    {
        if (count % saveCount == 0)
        {
            unit.SaveChanges();
        }
    }
}

EF对于批量插入非常糟糕,使用“INSERT SELECT”比EF快1000000倍。 - Akash Kava
2
你所做的不是“批量插入” - 你需要查看像EntityFramework.BulkInsert这样的组件或其他类似的组件(在你喜欢的搜索引擎上搜索“Entity Framework bulk insert” - 你会找到几个,选择你最喜欢的一个)。 - marc_s
1个回答

5
由于Entity Framework为每个记录执行数据库往返,因此它速度较慢。因此,如果您保存了200,000个实体,则将执行200,000次数据库往返,这远非保存多个实体的最佳选择。
针对这种情况,您需要自己实现或使用支持BulkInsert的库(通常在底层执行SqlBulkCopy)。
有三个主要的库(2个免费,1个专业版)可以进行批量插入。
// Example from Entity Framework Extensions Library
using (var ctx = new EntitiesContext())
{
    ctx.BulkInsert(list);
}

您可以阅读以下文章了解每个库的PROS和CONS:Entity Framework - Bulk Insert Library Reviews & Comparisons Entity Framework Extensions是目前提供最大灵活性(批量插入、更新、删除、合并和BulkSaveChanges以及支持所有内容)的库,但它是PRO版本。如果您正在寻找免费版本,我建议使用EntityFramework.BulkInsert,但它已不再受支持,并且不支持所有关联和继承。
免责声明:我是项目Entity Framework Extensions的所有者
编辑:回答评论问题

我每次保存100个记录,而不是每个记录

无论您向单元上下文中添加一个实体还是100个实体,Entity Framework都会逐一保存它们(每个记录一个单独的插入语句)。只需使用带有SQL Server数据库的SQL Profiler即可了解我的意思。
编辑:回答评论问题

太好了,乔纳森。是否有任何方法可以在ef6通用uow中实现这一点?

答案取决于您选择使用哪个库。
如果您使用我的库,可以创建BulkSaveChanges方法或在UnitOfWork中将所有“_context.SaveChanges()”更改为“_context.BulkSaveChanges()”。
public void SaveLeadsForBuyer(ISenderModel model)
{
    // ... code ...
    // _unit.SaveChanges();
    _unit.BulkSaveChanges();
}

如果您想获得最佳性能并从我的库或免费库实现批量插入,我可能会添加一个名为BulkInsert的方法或扩展方法(如果您无法更改存储库类)。

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
    // ... code ...

    public virtual void BulkInsert(List<TEntity> list)
    {
        _context.BulkInsert(list);
    }
}

请记住,BulkInsert可以直接插入实体而无需调用“SaveChanges”,它不使用上下文/更改跟踪器以获得最佳性能。


我是每100条记录保存一次,而不是每条记录保存一次。 - sensei
你使用了一个不适合这项工作的工具。EF 不适用于大批量传输。正如 Jonathan 所说,SqlBulkCopy 的作者应该被解雇(极其糟糕的锁定行为)...我在半天内编写了自己的类,创建了一个临时表,将 sql 大容量复制到其中,然后使用一个 sql 命令将数据复制到最终表中。避免了 SqlBulkCopy 中荒谬的锁定逻辑(它尝试在 30 秒内获得独占锁,在循环中不等待,因此如果表上有任何活动,它永远无法获得锁)。 - TomTom
乔纳森,有没有办法使用EF6通用UOW实现这个? - sensei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接