我正在处理一个非常大的数据集,大约有200万条记录。我有下面的代码,但是在它处理了三个批次,大约600,000条记录后得到了一个内存不足的例外。我理解,因为它循环遍历每个批处理实体框架的延迟加载,然后尝试将完整的200万条记录构建到内存中。有没有办法卸载我处理过的批次?
ModelContext dbContext = new ModelContext();
IEnumerable<IEnumerable<Town>> towns = dbContext.Towns.OrderBy(t => t.TownID).Batch(200000);
foreach (var batch in towns)
{
SearchClient.Instance.IndexMany(batch, SearchClient.Instance.Settings.DefaultIndex, "Town", new SimpleBulkParameters() { Refresh = false });
}
注意:Batch方法来自此项目:https://code.google.com/p/morelinq/
答案 0 :(得分:65)
问题在于,当您从EF获取数据时,实际上创建了两个数据副本,一个返回给用户,另一个EF保留并用于更改检测(这样它可以保持更改为数据库)。 EF在上下文的生命周期中持有第二个集合,它的这个集合会让你失去记忆。
您有2个选项来处理此
在查询中使用.AsNoTracking(),例如:
IEnumerable<IEnumerable<Town>> towns = dbContext.Towns.AsNoTracking().OrderBy(t => t.TownID).Batch(200000);
这告诉EF不要保留副本以进行变更检测。您可以在我的博客上阅读更多关于AsNoTracking的作用及其对性能的影响:http://blog.staticvoid.co.nz/2012/4/2/entity_framework_and_asnotracking
答案 1 :(得分:-1)
我编写了一个迁移例程,该例程从一个DB读取并(在布局上稍作更改)写入另一个DB(具有不同类型),在这种情况下,为每个批次更新连接并使用AsNoTracking()不会中断对我来说。
但是,以下算法确实解决了内存不足的问题:
每50行左右写入/更新,检查内存使用情况,根据需要恢复内存+重置输出数据库上下文(和连接的表):
var before = System.Diagnostics.Process.GetCurrentProcess().VirtualMemorySize64;
if (before > 800000000)
{
dbcontextOut.SaveChanges();
dbcontextOut.Dispose();
GC.Collect();
GC.WaitForPendingFinalizers();
dbcontextOut = dbcontextOutFunc();
tableOut = Dynamic.InvokeGet(dbcontextOut, outputTableName);
}