我需要使用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进行批量插入的有效方法?
答案 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
他探索了这些领域并比较了性能:
答案 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方法非常都很昂贵。
常见的解决方案是:
请参阅: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功能:
答案 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;
}