实体框架(EF)OutOfMemoryException

5

我从另一个来源接收到超过67000条记录。在应用业务规则后,我需要将它们存储到数据库中。我使用以下代码进行操作:

        using (var context = new MyEntities())
        {
            var importDataInfo = context.ImportDataInfoes.First(x => x.ID == importId);

            importedRecords.ForEach(importDataInfo.ValuationEventFulls.Add);

            context.SaveChanges();
        }

执行代码后,我得到了以下错误(OutOfMemoryException)。
    Error in executing code|Exception of type 'System.OutOfMemoryException' was thrown.*   at System.Data.Mapping.Update.Internal.KeyManager.<WalkGraph>d__5.MoveNext()
   at System.Data.Mapping.Update.Internal.KeyManager.GetPrincipalValue(PropagatorResult result)
   at System.Data.Mapping.Update.Internal.UpdateCompiler.GenerateValueExpression(EdmProperty property, PropagatorResult value)
   at System.Data.Mapping.Update.Internal.UpdateCompiler.BuildSetClauses(DbExpressionBinding target, PropagatorResult row, PropagatorResult originalRow, TableChangeProcessor processor, Boolean insertMode, Dictionary`2& outputIdentifiers, DbExpression& returning, Boolean& rowMustBeTouched)
   at System.Data.Mapping.Update.Internal.UpdateCompiler.BuildInsertCommand(PropagatorResult newRow, TableChangeProcessor processor)
   at System.Data.Mapping.Update.Internal.TableChangeProcessor.CompileCommands(ChangeNode changeNode, UpdateCompiler compiler)
   at System.Data.Mapping.Update.Internal.UpdateTranslator.<ProduceDynamicCommands>d__0.MoveNext()
   at System.Linq.Enumerable.<ConcatIterator>d__71`1.MoveNext()
   at System.Data.Mapping.Update.Internal.UpdateCommandOrderer..ctor(IEnumerable`1 commands, UpdateTranslator translator)
   at System.Data.Mapping.Update.Internal.UpdateTranslator.ProduceCommands()
   at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter)
   at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache)
   at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options)

我正在使用EF 4.0。

我的问题是,是否有保存记录数量的限制?保存大量记录的最佳实践是什么(将它们分块保存?事务如何处理?)。

提前感谢大家。

2个回答

4
您可能希望每次以1024条为一批发送数据。
您可以将批处理记录的循环包装在事务中,以便在需要时回滚整个序列。请注意,此事务很可能会升级为分布式事务。
分布式事务仅适用于运行Microsoft Distributed Transaction Coordinator(MS-DTC)服务的服务器。在处理分布式事务时,性能会受到明显的惩罚。

谢谢回复。- 是的,我已经更改了我的代码以测试它是否可以使用记录块工作,确实可以,但我的问题是我没有读到关于在EF中限制SaveChanges()记录数量的任何内容。你有听说过微软对此有什么限制吗? - Vlad Bezden
@Vlad Bezden,这个限制并不是与EF特别相关,而是.NET技术的一部分。当您ETL 67K条记录时,在内存中可能会有200k+个对象。您拥有67k原始记录,您拥有67k EF对象,并且每个对象最可能实例化上下文中的多个状态跟踪实体,所以在那里已经有67k + 67k + 67k,我也不会惊讶如果还会创建一些其他辅助对象。EF采用延迟查询模式工作,直到调用SaveChanges才开始真正的工作,这就是为什么会出现OOM的原因。 - Chris Marisic
我有一个类似的问题。不清楚该片段应该如何更改。如果能作为原始问题的更新展示出来就好了。我已经有现有的代码,其中在foreach循环内部有.Add,并且在循环后立即进行SaveChanges。不清楚在循环中包含SaveChanges是否总是会清除通过.Add添加的内容。 - MicMit
@MicMit 你想要批量更改服务器。所以对于X条记录,比如1024条,你想要每1024条记录调用一次SaveChanges。如果你使用for循环,你可以轻松地写出if(i%1024 == 0) context.SaveChanges()。然后你仍然需要在循环外部进行1次最终的savechanges调用,因为你可能没有一个完美的最后大小的批处理。当处理大型记录集时,你可能还想要查看yield return运算符并使用可枚举对象来避免将整个记录集加载到内存中。 - Chris Marisic
@Chris,由于某些原因,对于10个批次没有任何区别。您知道有关您建议的方法的代码示例的任何参考资料吗?您实际上是指覆盖/重写savechanges或添加吗? - MicMit
显示剩余3条评论

4
一般来说,.NET在单个集合或其他对象中的内存限制为2GB。这是因为即使在64位环境下,索引器也使用32位整数(最大值为20亿多)。即使对于像整数这样的简单数据类型,一个int的大小意味着只能在单个数组中存储5亿个int。对于更大的值类型,如结构,集合的最大元素数量变得非常小。
如果您处于Windows XP等32位环境中,则限制更低;整个程序的最大内存空间不能超过2GB。这对像您这样的ETL施加了一些相当高的限制,我不会感到惊讶,您的程序试图一次处理67k个内存记录时会耗尽内存。
解决方案是,如果可以,请将记录分批处理。尝试基于ID构建语句,返回ID大于您已检索到的最大ID的前100条记录。处理完记录后,处理掉它(或者只是让GC去做它的工作)。

谢谢回复。当我调用SaveChanges方法时,我遇到了OOME错误。在我的应用程序的.NET代码方面,我没有任何问题,我测试了处理超过1,000,000个导入记录,系统运行良好,只有在我调用EF上的SaveChanges时才会失败。 - Vlad Bezden
可能是因为像EF这样的ORM需要确定更改内容,以便可以生成有效的UPDATE语句。这需要再次提取记录,并将更改存储在某个集合中,以便用于生成更新。此外,形成更新命令的字符串是字符集合,将67,000乘以多达几千个字符每个UPDATE,您很容易达到字符串的长度限制。同样,尝试为传入和传出记录设置“批处理大小”,这将限制传入数据的数量和传出命令的大小。 - KeithS
抱歉晚来一步:是SaveChanges()调用导致了OOME吗 - 所以如果我更频繁地调用SaveChanges,我就可以最小化这种风险?我有一个foreach循环,但在保存之前我处理了所有项目,而没有任何阻止我逐个保存每个项目。 - BlueChippy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接