5

我需要使用 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 进行批量插入的有效方法?

4

9 回答 9

13

验证通常是 EF 的一个非常昂贵的部分,通过禁用它,我获得了很大的性能改进:

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

我相信我在一个类似的 SO 问题中发现了这一点——也许是这个答案

关于这个问题的另一个答案正确地指出,如果你真的需要批量插入性能,你应该考虑使用System.Data.SqlClient.SqlBulkCopy. 对于这个问题,在 EF 和 ADO.NET 之间进行选择实际上取决于您的优先级。

于 2013-09-03T20:46:24.627 回答
2

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

在每次添加 100 个项目后调用 SaveChanges。我感觉 EF 中的 Track Changes 在处理大量数据时表现非常糟糕。

于 2013-09-03T20:33:50.307 回答
2

我会推荐这篇关于如何使用 EF 进行批量插入的文章。

实体框架和缓慢的批量插入

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

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

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

常见的解决方案是:

  • 使用 AddRange 而不是 Add
  • 将 AutoDetectChanges 设置为 false
  • 多批次拆分 SaveChanges

请参阅:改进实体框架添加性能

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

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

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

请参阅:实体框架批量插入库


免责声明:我是实体框架扩展的所有者

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

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

例子

// 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;
});
于 2017-01-06T14:45:21.160 回答
1

EF 并不是真正可用于批处理/批量操作(我认为一般 ORM 不是)。

运行如此缓慢的特殊原因是 EF 中的更改跟踪器。几乎每次调用 EF API 都会在内部调用 TrackChanges(),包括 DbSet.Add()。当您添加 2500 时,此函数将被调用 2500 次。而且每次调用都变得越来越慢,您添加的数据越多。因此禁用 EF 中的更改跟踪应该会有很大帮助:

dataContext.Configuration.AutoDetectChangesEnabled = false;

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

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

于 2013-09-03T20:59:19.240 回答
1

虽然这有点晚了,上面发布的答案和评论非常有用,但我将把它留在这里,希望它对那些和我有同样问题并来到这篇文章寻求答案的人有用。如果您搜索使用 Entity Framework 批量插入记录的方法,这篇文章在 Google 上的排名仍然很高(在发布此答案时)。

我在 MVC 5 应用程序中使用 Entity Framework 和 Code First 时遇到了类似的问题。我让一个用户提交了一个表单,导致数万条记录被插入到一个表中。在插入 60,000 条记录时,用户不得不等待超过 2 分半钟。

经过多次谷歌搜索,我偶然发现了BulkInsert-EF6,它也可以作为 NuGet 包使用。修改 OP 的代码:

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

using (var transaction = context.Transaction())
{
    try
    {
        context.BulkInsert(tempItemList);
        transaction.Commit();
    }
    catch (Exception ex)
    {
        // Handle exception
        transaction.Rollback();
    }
}

对于 60,000 条记录,我的代码从花费 > 2 分钟到小于 1 秒。

于 2016-09-15T16:17:23.923 回答
1

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

https://github.com/MHanafy/EntityExtensions

还有其他好东西,希望它会被扩展以在轨道上做更多的事情。

使用它就像

var insertsAndupdates = new List<object>();
var deletes = new List<object>();
context.BulkUpdate(insertsAndupdates, deletes);

希望能帮助到你!

于 2017-10-16T21:49:12.593 回答
0

EF6 beta 1 有一个可能适合您的目的的 AddRange 函数:

使用 Entity Framework 6 beta 1 插入多行

EF6 将在“今年”发布(2013 年)

于 2013-09-04T07:58:44.523 回答
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;
    }
于 2017-01-06T03:42:25.150 回答