EF Code First Bulk Insert

时间:2013-09-03 19:45:27

标签: c# sql-server entity-framework ef-code-first

我需要使用EF Code First插入大约2500行。

我的原始代码看起来像这样:

foreach(var item in listOfItemsToBeAdded)
{
    //biz logic
    context.MyStuff.Add(i);
}

这花了很长时间。每次DBSet.Add()电话约为2.2秒,相当于约90分钟。

我重构了这个代码:

var tempItemList = new List<MyStuff>();
foreach(var item in listOfItemsToBeAdded)
{
    //biz logic
    tempItemList.Add(item)
}
context.MyStuff.ToList().AddRange(tempItemList);

这只需要大约4秒钟才能运行。但是,.ToList()查询当前表中的所有项目,这是非常必要的,从长远来看可能是危险的,甚至更耗时。一种解决方法是执行类似context.MyStuff.Where(x=>x.ID = *empty guid*).AddRange(tempItemList)的操作,因为我知道永远不会有任何返回。

但我很好奇是否有其他人知道使用EF Code First进行批量插入的有效方法?

9 个答案:

答案 0 :(得分:13)

验证通常是EF的一个非常昂贵的部分,通过以下方式禁用它,我获得了很大的性能提升:

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

我相信我在类似的问题中发现了这个问题 - perhaps it was this answer

关于该问题的

Another answer正确地指出,如果您确实需要批量插入性能,则应该使用System.Data.SqlClient.SqlBulkCopy。 EF和ADO.NET之间针对此问题的选择实际上围绕着您的优先事项。

答案 1 :(得分:2)

我有一个疯狂的想法,但我认为它会对你有帮助。

每次添加100项后,请调用SaveChanges。我有一种感觉跟踪EF的变化与巨大的数据有很差的表现。

答案 2 :(得分:2)

我建议这篇文章介绍如何使用EF进行批量插入。

Entity Framework and slow bulk INSERTs

他探索了这些领域并比较了性能:

  1. 默认EF(完成添加30,000条记录需要57分钟)
  2. 替换为ADO.NET代码(25 代表相同的30,000)
  3. 上下文膨胀 - 通过为每个工作单元使用新的上下文(相同的30,000次插入需要33秒)来保持活动的上下文图形小。
  4. 大型列表 - 关闭AutoDetectChangesEnabled(将时间缩短至约20秒)
  5. 批量处理(最短16秒)
  6. DbTable.AddRange() - (性能在12范围内)

答案 3 :(得分:1)

EF实际上并不适用于批量/批量操作(我认为通常不会使用ORM)。

这种运行速度如此之慢的特殊原因是因为EF中的更改跟踪器。实际上,每次调用EF API都会在内部调用TrackChanges(),包括DbSet.Add()。添加2500时,此函数将被调用2500次。每次调用都越来越慢,添加的数据越多。因此,禁用EF中的更改跟踪应该会有很大帮助:

dataContext.Configuration.AutoDetectChangesEnabled = false;

更好的解决方案是将大批量操作拆分为2500个较小的事务,每个事务都使用自己的数据上下文运行。您可以使用msmq或其他一些可靠消息传递机制来启动每个较小的事务。

但是,如果您的系统是围绕批量操作构建的,我建议您为数据访问层寻找与EF不同的解决方案。

答案 4 :(得分:1)

正如STW指出的那样,每次调用Add方法时调用的DetectChanges方法非常都很昂贵。

常见的解决方案是:

  • 使用AddRange而不是添加
  • SET AutoDetectChanges to false
  • SPLIT多个批次的SaveChanges

请参阅:Improve Entity Framework Add Performance

重要的是要注意使用AddRange不执行BulkInsert,它只需调用一次DetecthChanges方法(在添加所有实体之后),这将大大提高性能。

  

但我很好奇是否有其他人知道有效的批量方式   使用EF Code First

插入

有一些支持批量插入的第三方库:

请参阅:Entity Framework Bulk Insert library

免责声明:我是Entity Framework Extensions

的所有者

此库允许您执行场景所需的所有批量操作:

  • 批量保存更改
  • 批量插入
  • 批量删除
  • 批量更新
  • 批量合并

实施例

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);

// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);

// Customize Primary Key
context.BulkMerge(customers, operation => {
   operation.ColumnPrimaryKeyExpression = 
        customer => customer.Code;
});

答案 5 :(得分:1)

虽然迟到了回复,但我发布了答案,因为我遭受了同样的痛苦。 我已经为此创建了一个新的GitHub项目,截至目前,它支持使用SqlBulkCopy透明地为Sql服务器批量插入/更新/删除。

https://github.com/MHanafy/EntityExtensions

还有其他好东西,希望它会延伸到更多的轨道。

使用它就像

一样简单
SIZE_MAX + 1

希望它有所帮助!

答案 6 :(得分:0)

EF6 beta 1具有适合您目的的AddRange功能:

INSERTing many rows with Entity Framework 6 beta 1

EF6 will be released "this year"(2013)

答案 7 :(得分:0)

虽然这有点晚了,上面发布的答案和评论非常有用,但我会把它留在这里并希望它对那些对我们有用的人有用。 和我一样有同样的问题,来这篇文章寻求答案。这个帖子 如果你搜索一种方式,谷歌(在发布这个答案的时候)仍然排名很高 使用Entity Framework批量插入记录。

我在MVC 5应用程序中使用Entity Framework和Code First时遇到了类似的问题。我有一个用户提交了一个导致成千上万条记录的表单 插入表格。用户必须等待超过2.5分钟,同时插入60,000条记录。

经过大量的谷歌搜索,我偶然发现了BulkInsert-EF6,这也是可用的 作为NuGet包。重写OP的代码:

a

我的代码从> 2分钟变为<1秒,持续60,000条记录。

答案 8 :(得分:0)

    public static void BulkInsert(IList list, string tableName)
    {
        var conn = (SqlConnection)Db.Connection;
        if (conn.State != ConnectionState.Open) conn.Open();

        using (var bulkCopy = new SqlBulkCopy(conn))
        {
            bulkCopy.BatchSize = list.Count;
            bulkCopy.DestinationTableName = tableName;

            var table = ListToDataTable(list);
            bulkCopy.WriteToServer(table);
        }
    }

    public static DataTable ListToDataTable(IList list)
    {
        var dt = new DataTable();
        if (list.Count <= 0) return dt;

        var properties = list[0].GetType().GetProperties();
        foreach (var pi in properties)
        {
            dt.Columns.Add(pi.Name, Nullable.GetUnderlyingType(pi.PropertyType) ?? pi.PropertyType);
        }

        foreach (var item in list)
        {
            DataRow row = dt.NewRow();
            properties.ToList().ForEach(p => row[p.Name] = p.GetValue(item, null) ?? DBNull.Value);
            dt.Rows.Add(row);
        }
        return dt;
    }