当我的 LINQ 查询将数据库记录作为 Enumerable 获取并在 foreach 循环中访问记录时会发生什么?

What happens when my LINQ query gets database records as Enumerable and accesses records in a foreach loop?

我有这样的代码:

public class Database : System.Data.Entity.DbContext
{
    public DbSet<Person> Persons { get; set; }
}

var db = new Database();
var persons = db.Persons.Where(...).AsEnumerable();
foreach(var person in persons)
{
    //...
}

以下哪种情况是正确的?

第一种情况是正确的;应用程序将使用对数据库的单个请求从与您的 where 子句匹配的人员 table 获取记录集,然后从内存中访问每条记录。

当然,"under the hood"比这要复杂一点。然而,虽然应用程序可能会一条一条地接收记录,但只会在数据库上执行一个查询——如下面的探查器屏幕截图所示。

AsEnumerable 不执行查询,因为 AsEnumerable 保留延迟执行,只是将您的集合转换为 IEnumerable。

查询将在循环开始时执行,因为这是您请求数据的地方。

foreach(var person in persons) // <- query executes here
{
    //...
}

一个简单的测试方法是连接 SQL Server Profiler 并检查在数据库上执行的查询:

如您所见,只执行了一个查询。

如果集合中的对象包含子对象,它将执行查询以获取这些子对象,因为默认情况下 EF 延迟加载结果集。

添加 .ToList() 将强制查询稍微提早执行:

var persons = db.Persons.Where(...).ToList();

Which of the following scenarios is correct?

  • Application fetches entire records from Persons table by one request to database, then access each record from memory.
  • In each step of foreach loop, application fetches only one record from database.

None 这些说法是完全正确的。但首先,让我说在你的代码中 .AsEnumerable() 实际上没有做任何事情。您可以删除它而无需从逻辑上更改任何内容。 IQueryable 实现了 IEnumerableforeach executes IEnumerable methods under the hood。所以它解决了IQueryable“可枚举”的问题。

现在是阅读部分。从应用程序的角度来看,第二种说法最接近事实。它一一接收 persons 中的所有实体。不是在循环结束之前所有 persons 都可用。

但实际的低层次阅读是分块进行的。如 here 所述,客户端将来自数据库的原始数据存储在网络缓冲区中。根据这些缓冲区的大小和结果集的大小(即 persons 的数量和大小), 可能一次读取所有记录。 “大量”数据将需要多次读取。

对于应用程序来说,这并不重要。当谈到性能优化时,我认为我们应该想到的最后一件事就是玩弄网络缓冲区大小。因此,为了更正确地改写第二个陈述:

  • 在 foreach 循环的每一步中,只有一条记录从数据库发送到应用程序的范围。