我正在寻找插入实体框架的最快方法。
我之所以问这个问题,是因为您有一个活动的TransactionScope,并且插入量很大(4000+)。它可能会持续超过10分钟(事务的默认超时),这将导致事务不完整。
我正在寻找插入实体框架的最快方法。
我之所以问这个问题,是因为您有一个活动的TransactionScope,并且插入量很大(4000+)。它可能会持续超过10分钟(事务的默认超时),这将导致事务不完整。
当前回答
下面是使用实体框架和使用SqlBulkCopy类之间的性能比较,具体示例为:如何将复杂对象批量插入SQL Server数据库
正如其他人已经强调的,ORM不应用于批量操作。它们提供了灵活性、关注点分离和其他好处,但批量操作(批量读取除外)不是其中之一。
其他回答
您应该考虑为此使用System.Data.SqlClient.SqlBulkCopy。这是文档,当然还有很多在线教程。
抱歉,我知道您正在寻找一个简单的答案来让EF做您想做的事情,但批量操作并不是ORM真正的用途。
我已经研究了Slauma的答案(这太棒了,感谢创意人),我已经减少了批量,直到达到最佳速度。查看Slauma的结果:
commitCount=1,recreateContext=true:超过10分钟commitCount=10,recreateContext=true:241秒commitCount=100,recreateContext=true:164秒commitCount=1000,recreateContext=true:191秒
可以看出,当从1移动到10,以及从10移动到100时,速度会增加,但从100到1000的插入速度会再次下降。
因此,我重点关注了当您将批量大小减少到10到100之间时会发生什么,下面是我的结果(我使用了不同的行内容,因此我的时间值不同):
Quantity | Batch size | Interval
1000 1 3
10000 1 34
100000 1 368
1000 5 1
10000 5 12
100000 5 133
1000 10 1
10000 10 11
100000 10 101
1000 20 1
10000 20 9
100000 20 92
1000 27 0
10000 27 9
100000 27 92
1000 30 0
10000 30 9
100000 30 92
1000 35 1
10000 35 9
100000 35 94
1000 50 1
10000 50 10
100000 50 106
1000 100 1
10000 100 14
100000 100 141
根据我的结果,批量大小的实际最佳值约为30。它小于10和100。问题是,我不知道为什么30是最优的,也找不到任何合理的解释。
下面是使用实体框架和使用SqlBulkCopy类之间的性能比较,具体示例为:如何将复杂对象批量插入SQL Server数据库
正如其他人已经强调的,ORM不应用于批量操作。它们提供了灵活性、关注点分离和其他好处,但批量操作(批量读取除外)不是其中之一。
对于您在问题评论中的评论:
“…保存更改(每个记录)。。。"
这是你能做的最糟糕的事情!对每个记录调用SaveChanges()会大大降低批量插入的速度。我会做一些简单的测试,这很可能会提高性能:
在所有记录后调用SaveChanges()一次。例如,在100条记录之后调用SaveChanges()。例如,在100条记录之后调用SaveChanges(),并释放上下文并创建新的上下文。禁用更改检测
对于批量插入,我正在使用这样的模式进行工作和试验:
using (TransactionScope scope = new TransactionScope())
{
MyDbContext context = null;
try
{
context = new MyDbContext();
context.Configuration.AutoDetectChangesEnabled = false;
int count = 0;
foreach (var entityToInsert in someCollectionOfEntitiesToInsert)
{
++count;
context = AddToContext(context, entityToInsert, count, 100, true);
}
context.SaveChanges();
}
finally
{
if (context != null)
context.Dispose();
}
scope.Complete();
}
private MyDbContext AddToContext(MyDbContext context,
Entity entity, int count, int commitCount, bool recreateContext)
{
context.Set<Entity>().Add(entity);
if (count % commitCount == 0)
{
context.SaveChanges();
if (recreateContext)
{
context.Dispose();
context = new MyDbContext();
context.Configuration.AutoDetectChangesEnabled = false;
}
}
return context;
}
我有一个测试程序,它将560.000个实体(9个标量财产,没有导航财产)插入数据库。使用此代码,它只需不到3分钟即可工作。
为了提高性能,在“许多”记录之后调用SaveChanges()非常重要(“许多”约为100或1000)。它还提高了在SaveChanges之后释放上下文并创建新上下文的性能。这将从所有实体中清除上下文,SaveChanges不会这样做,实体仍然以状态Unchanged附加到上下文。正是由于上下文中附加实体的大小不断增加,才导致插入过程一步步变慢。因此,在一段时间后清除它是有帮助的。
以下是我的560000个实体的一些测量值:
commitCount=1,recreateContext=false:许多小时(这是您当前的过程)commitCount=100,recreateContext=false:超过20分钟commitCount=1000,recreateContext=false:242秒commitCount=10000,recreateContext=false:202秒commitCount=100000,recreateContext=false:199秒commitCount=1000000,recreateContext=false:内存不足异常commitCount=1,recreateContext=true:超过10分钟commitCount=10,recreateContext=true:241秒commitCount=100,recreateContext=true:164秒commitCount=1000,recreateContext=true:191秒
上述第一个测试中的行为是,性能非常非线性,并且随着时间的推移会大大降低。(“很多小时”是一个估计,我从未完成过这项测试,20分钟后我停在了50000个实体。)这种非线性行为在所有其他测试中都不那么重要。
尝试使用存储过程来获取要插入的数据的XML。