当我的 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)
{
//...
}
以下哪种情况是正确的?
- 应用程序通过对数据库的一次请求从
Persons
table 中获取整条记录,然后从内存中访问每条记录。
- 在
foreach
循环的每一步中,应用程序只从数据库中获取一条记录。
第一种情况是正确的;应用程序将使用对数据库的单个请求从与您的 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
实现了 IEnumerable
和 foreach
executes IEnumerable
methods under the hood。所以它解决了IQueryable
“可枚举”的问题。
现在是阅读部分。从应用程序的角度来看,第二种说法最接近事实。它一一接收 persons
中的所有实体。不是在循环结束之前所有 persons
都可用。
但实际的低层次阅读是分块进行的。如 here 所述,客户端将来自数据库的原始数据存储在网络缓冲区中。根据这些缓冲区的大小和结果集的大小(即 persons
的数量和大小), 是 可能一次读取所有记录。 “大量”数据将需要多次读取。
对于应用程序来说,这并不重要。当谈到性能优化时,我认为我们应该想到的最后一件事就是玩弄网络缓冲区大小。因此,为了更正确地改写第二个陈述:
- 在 foreach 循环的每一步中,只有一条记录从数据库发送到应用程序的范围。
我有这样的代码:
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)
{
//...
}
以下哪种情况是正确的?
- 应用程序通过对数据库的一次请求从
Persons
table 中获取整条记录,然后从内存中访问每条记录。 - 在
foreach
循环的每一步中,应用程序只从数据库中获取一条记录。
第一种情况是正确的;应用程序将使用对数据库的单个请求从与您的 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
实现了 IEnumerable
和 foreach
executes IEnumerable
methods under the hood。所以它解决了IQueryable
“可枚举”的问题。
现在是阅读部分。从应用程序的角度来看,第二种说法最接近事实。它一一接收 persons
中的所有实体。不是在循环结束之前所有 persons
都可用。
但实际的低层次阅读是分块进行的。如 here 所述,客户端将来自数据库的原始数据存储在网络缓冲区中。根据这些缓冲区的大小和结果集的大小(即 persons
的数量和大小), 是 可能一次读取所有记录。 “大量”数据将需要多次读取。
对于应用程序来说,这并不重要。当谈到性能优化时,我认为我们应该想到的最后一件事就是玩弄网络缓冲区大小。因此,为了更正确地改写第二个陈述:
- 在 foreach 循环的每一步中,只有一条记录从数据库发送到应用程序的范围。