【问题标题】:How to deal with large result sets with Linq to Entities?如何使用 Linq to Entities 处理大型结果集?
【发布时间】:2010-06-16 17:39:19
【问题描述】:

我有一个相当复杂的 linq 到我在网站上显示的实体查询。它使用分页,所以我从不会一次拉下超过 50 条记录。

但我还想为用户提供将完整结果导出为 Excel 或其他文件格式的选项。

我担心的是,为了执行此操作,可能会同时将大量记录全部加载到内存中。

有没有一种方法可以像使用数据读取器一样一次处理 linq 结果集 1 条记录,因此一次只有 1 条记录真正保存在内存中?

我看到了一些建议,即如果您使用 foreach 循环枚举 linq 查询,记录将不会一次全部读入内存,也不会使服务器不堪重负。

有没有人有我可以阅读的内容的链接来验证这一点?

我将不胜感激。

谢谢

【问题讨论】:

  • 默认情况下,结果不应该是内存中的集合 - 您是否正在做某事(ToArray、ToList 等)将其全部带入内存?如果没有,只需遍历结果(foreach、Select 等)就可以了。
  • 目前我的查询方法确实调用 ToList() 并返回列表,但我可以轻松地更改它以返回 QueryObject。您确定如果我在 IQueryable 上执行 foreach 循环,它会流式传输数据吗?
  • 所以你的ToList() 基本上会做类似List<Object> list = new List<Object>(); foreach (Object object in query) { list.Add(object); } 的事情。 foreach 将调用 query.MoveNext()。因此,您将所有内容加载到列表中并增加内存使用量。 (我跳过了 GetEnumerator() 部分等,所以这不准确)

标签: c# linq linq-to-entities


【解决方案1】:

set the ObjectContext to MergeOption.NoTracking(因为它是只读操作)。如果您使用相同的ObjectContext 来保存其他数据,请在上下文中使用Detach the object

如何分离

foreach( IQueryable)
{
  //do something 
  objectContext.Detach(object);
}

编辑:如果您使用NoTracking选项,则无需分离

Edit2:我写信给Matt Warren 关于这个场景。并在他的同意下在这里发布相关的私人信件

来自 SQL Server 的结果可能不会 甚至全部由服务器生产 然而。查询已开始于 服务器和第一批结果 被转移到客户,但没有 产生更多(或者它们被缓存 在服务器上)直到客户端 请求继续阅读它们。 这就是所谓的“消防水带” 光标模式,或有时称为 作为流媒体。服务器正在发送 他们尽可能快,客户 正在尽可能快地阅读它们 (您的代码),但有一个数据 下面的传输协议 需要得到对方的承认 客户端继续发送更多数据。

由于IQueryable 继承自IEnumerable,我相信发送到服务器的底层查询将是相同的。但是,当我们执行IEnumerable.ToList() 时,底层连接使用的数据读取器将开始填充对象,对象被加载到应用程序域中,并且可能会耗尽内存,这些对象还不能被释放。

当您使用foreachIEunmerable 时,数据读取器一次读取一个SQL 结果集,创建对象然后释放对象。底层连接可能会以块的形式接收数据,并且在读取所有块之前可能不会将响应发送回 SQL Server。因此,您不会遇到“内存不足”异常

编辑3

当您的查询运行时,您实际上可以打开 SQL Server“活动监视器”并查看查询,任务状态为 SUSPENDED,等待类型为 Async_network_IO - 这实际上表明结果在 SQL Server 网络中缓冲。你可以阅读更多关于它的信息herehere

【讨论】:

  • 是的,设置 NoTracking 也应该有帮助。你知道是否有办法在整个 Context 上设置它?我刚刚在内容内的 ObjectSet 上设置了它。
  • 您在寻找objectContext.MergeOption = MergeOption.NoTracking 吗? msdn.microsoft.com/en-us/library/bb738896.aspx
  • 我认为 ObjectContext 没有 MergeOption 属性。您是否正在考虑使用 ObjectQuery?
  • 对不起,我错了,桌子上objectContext.Table.MergeOption=MergeOption.NoTracking是正确的做法
  • 好的。现在,假设您的示例代码中的 foreach 循环需要迭代 100 万次。我只想检查所有 100 万条记录是否最终都不会出现在服务器的内存中,但是在每次迭代中只有 1 个“行”被加载,然后在下一次迭代中被丢弃,只要我的代码不选择存储数据。你能确认是这样吗?
【解决方案2】:

查看 LINQ 查询的返回值。应该是IEnumerable<>,一次只加载一个对象。如果你随后使用.ToList() 之类的东西,它们都会被加载到内存中。只要确保您的代码不维护一个列表或一次使用多个实例,您就可以了。

编辑:补充人们对 foreach 的看法...如果您执行以下操作:

var query = from o in Objects
            where o.Name = "abc"
            select o;

foreach (Object o in query)
{
   // Do something with o
}

查询部分使用延迟执行 (see examples),因此对象尚未在内存中。 foreach 遍历结果,但一次只获取一个对象。 query 使用 IEnumerator,它有 Reset()MoveNext()。 foreach 每轮调用 MoveNext() 直到没有更多结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 2010-10-22
    • 2010-12-04
    • 2012-11-10
    相关资源
    最近更新 更多