如何在使用Entity Framework和LINQ查询大型数据集时避免内存溢出问题

18

我有一个处理所有数据库方法的类,包括Entity Framework相关内容。当需要数据时,其他类可能会调用这个类中的方法,例如:

public List<LocalDataObject> GetData(int start, int end);

使用LINQ to EF进行数据库查询后,调用类可以遍历数据。但由于其他类无法访问EF中的实体,我需要在查询上执行“ToList()”操作,并将完整数据集获取到内存中。

如果这个集合非常大(10-100 GB),会发生什么?

有没有更有效的迭代方式,同时仍然保持松散耦合?


1
即使您不使用ToList()查询...一旦您“触摸”它,它将从数据库中获取所有行并存储在内存中。 - Lee Gunn
2
您想要对结果进行“分页”吗? - Lee Gunn
关于Entity Framework 5的性能考虑,您可以查看以下链接:http://msdn.microsoft.com/en-us/data/hh949853 - surfmuggle
5个回答

22

在Entity Framework中处理大型数据集的正确方法是:

  • 使用EFv4和POCO对象 - 它将允许与上层共享对象,而不引入对Entity Framework的依赖
  • 关闭代理创建/延迟加载以完全分离POCO实体与对象上下文
  • 公开IQueryable<EntityType>以允许上层更精确地指定查询并限制从数据库加载的记录数
  • 在公开IQueryable时,在您的数据访问方法中的ObjectQuery上设置MergeOption.NoTracking。将此设置与关闭代理创建相结合应该导致未缓存的实体,并且对查询结果进行的迭代应始终仅加载单个已材料化的实体(而不缓存已加载的实体)。

在简单的场景中,您可以始终检查客户端是否请求了过多的记录,并只返回最多允许的记录或触发异常。


1
我认为你所说的“大型”和Saul所说的“大型”是非常不同的。我认为他的问题无法通过关闭跟踪来解决。最终,这个单一实体的所有数据仍然会被加载到内存中。 - Euphoric
1
但这需要比您建议的更多。EF仍会将所有数据加载到内存中。或者EF内部有我不知道的隐藏函数,可以分页数据吗? - Euphoric
@Ladislav Mrnka,我使用您的规范在这里做了一个简单的Northwind示例,并使用SQL Profiler,我看到针对数据库的查询在第一个被foreach访问的记录上完成了(我在那里设置了断点)。您确定对象不都在内存中吗?我找不到有关该行为的文档。 - Menahem
@Saul:那么你的担忧是非常合理的,因为你的架构无法解决这个问题。 - Ladislav Mrnka
@Menahem:Ladislav 是对的,如果你使用 NoTracking 标志(或者在 EF 4.1 中使用 AsNoTracking() 扩展方法)来查询数据,并且没有代理和延迟加载,那么在加载数据时 ObjectContext 仍然为空。你可以用 DbContext.ChangeTracker.Entries().Count() 来检查这一点(在 EF 4.1 中)(注意:此调用非常缓慢,只适合测试)。它将始终为0。我有一个测试项目,其中以 Ladislav 描述的方式遍历了一个表中超过一百万条记录。它只需要1.5秒,内存消耗不会增加。 - Slauma
显示剩余8条评论

6
虽然我喜欢EF用于快速/简单的数据访问,但对于这种情况,我可能不会使用它。当处理如此大量的数据时,我会选择存储过程来返回所需的内容,而且没有多余的内容。然后使用轻量级的DataReader来填充对象。
DataReader提供了一个未缓存的数据流,允许过程逻辑顺序地高效处理来自数据源的结果。DataReader在检索大量数据时是一个不错的选择,因为数据不会被缓存在内存中。
另外,就内存管理而言,当然要确保将处理非托管资源的代码包装在using块中,以便进行正确的处理/垃圾回收。
您还可以考虑实现分页。

每当你引入第三方库(我仍然认为EF是其中之一)时,你总会有一定程度的开销影响效率/性能。话虽如此,我不知道,我从未研究过这个问题。 - Kon

4

关于这一点的简短说明:

但是由于其他类无法访问EF中的实体,我需要在查询上执行"ToList()"操作,并将完整的数据集提取到内存中。

在我看来,你在这里遇到的问题与EF无关。如果您不使用EF进行数据访问而使用原始ADO.NET呢?"没有访问EF"则转化为"没有访问数据库连接"

如果您有必须处理大量对象但无法访问数据库的服务或方法 - 无论是通过EF还是其他类型的数据库连接 - 您必须在将它们传递给这些服务/方法之前将数据加载到内存中。然后,您可以考虑解决方案以在客户端硬盘上缓冲加载的数据,但这与数据源及其检索方式无关。如果您不缓冲和加载所有内容到内存中,则受可用内存的限制。我认为没有逃脱这种限制的方法。

如果您连接到EF,则我认为Ladislav答案中提供的解决方案是正确的,因为他描述的设置和过程几乎将EF降至简单DataReader的行为。


0

我会使用lazy IEnumerable并且在内部实现分页。

也许您可以创建自己的IEnumerable接口,这样您库的用户就不会被诱惑自己调用ToList。

但问题是...这是否真的是隐藏事实的好方法,即此数据层的用户将使用如此大量的数据?首要任务是将返回的数据限制到最少。不要返回整个实体,而只返回您实际需要的部分。您是否急于获取任何相关实体?您有没有考虑使用延迟加载?


0

确保的一种方法是始终设置一个上限阈值,以避免通过在查询末尾加上 .Take(MAX_ROWS) 返回庞大的集合。这可以是一个解决方案或预防措施,以避免不良调用导致服务崩溃,但最好重新考虑解决方案。


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