如何使用实体框架和 LINQ 查询大型数据集时避免内存溢出

本文关键字:数据集 溢出 内存 大型 查询 实体 何使用 框架 LINQ | 更新日期: 2023-09-27 17:55:15

我有一个处理所有数据库方法的类,包括与实体框架相关的东西。当需要数据时,其他类可能会调用此类中的方法,例如

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

数据库使用 LINQ to EF 进行查询,然后调用类可以循环访问数据。但由于其他类无法访问 EF 中的实体,因此我需要对查询执行"ToList()"操作,然后将完整的数据集提取到内存中。

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

有没有更有效的方法来进行迭代并仍然保持松散耦合?

如何使用实体框架和 LINQ 查询大型数据集时避免内存溢出

在实体框架中处理大型数据集的正确方法是:

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

在简单方案中,您始终可以检查客户端是否没有询问太多记录,只需触发异常或仅返回允许的最大记录数。

尽管我喜欢 EF 进行快速/简单的数据访问,但我可能不会将其用于这种情况。 在处理这种大小的数据时,我会选择完全返回您需要的内容的存储过程,而不是额外的内容。 然后使用轻量级 DataReader 填充对象。

数据读取器提供无缓冲 允许程序化的数据流 高效处理结果的逻辑 按顺序从数据源。这 数据阅读器是一个不错的选择,当 检索大量数据 因为数据未缓存在 记忆。

此外,就内存管理而言,当然要确保将处理非托管资源的代码包装在 using 块中,以便正确处理/垃圾回收。

您可能还需要考虑实现分页。

关于这一点的快速说明:

但是由于其他类无法访问 到 EF 中的实体,我需要 对 查询,并通过它获取完整的 数据集到内存中。

在我看来,您在这里遇到的问题与 EF 完全无关。如果不使用 EF 进行数据访问,而是使用原始 ADO.NET,您会怎么做?"无权访问 EF"则转换为"无权访问数据库连接"。

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

如果您与 EF 有联系,我认为 Ladislav 的答案中提供的解决方案是正确的,因为他描述的设置和过程将 EF 几乎简化为简单 DataReader 的行为。

一种确定的方法是始终设置一个上限阈值,您将返回该上限,以避免通过以 .Take(MAX_ROWS) 结束查询来避免巨大的设置。这可能是一种解决方法,也可以是关闭服务的错误呼叫的预防性举措,但最好的做法是重新考虑解决方案

我会使用惰性的IEnumerable并在内部为您的数据实现某种分页。

也许创建你自己的IEnumerable接口,这样你的库的用户就不会试图自己调用ToList。

但问题是......隐藏事实真的是好方法吗,这个数据层的用户会处理这样的数据吗?首先要做的是将返回的数据限制为最低限度。不要返回您真正需要的整个实体,而只返回部分。您是否急于获取任何相关实体?您是否考虑过使用延迟加载?