2017-04-23 9 views
1

我有这样的代码:当我的LINQ查询获取数据库记录为Enumerable并访问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表,然后从存储器访问的每个记录。
  • foreach循环的每一步中,应用程序仅从数据库中提取一条记录。
+0

您的'db.Persons.Where(...)'将生成一个sql查询并执行where语句服务器端。这些行将流式传输给您的客户端。 (do ** not **使用'.ToList()'或者其他等, –

回答

3

下列哪种情况下是正确的?

  • 应用程序通过一个请求从数据库中提取人员表中的整个记录​​,然后从内存中访问每条记录。
  • 在foreach循环的每一步中,应用程序只从数据库中提取一条记录。

以上都不是完全正确的。但首先,让我说,在你的代码.AsEnumerable()实际上没有做任何事情。您可以在不改变任何内容的情况下将其删除。 IQueryable实施IEnumerableforeach executes IEnumerable methods under the hood。所以它将IQueryable称为“可枚举”。

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

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

对于应用程序,这并不重要。在性能优化方面,我认为我们应该考虑的最后一件事就是使用网络缓冲区大小。因此,要更正确地重新说明第二条语句:

  • 在foreach循环的每一步中,只有一条来自数据库的记录被发送到应用程序的作用域。
2

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

当然,“引擎盖下”比这更复杂一点。但是,尽管应用程序可能会逐个接收记录,但在数据库上只执行一个查询 - 正如下面的分析器屏幕截图所示。

AsEnumerable不会执行查询,因为AsEnumerable可以保留延迟执行并将您的集合转换为IEnumerable。

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

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

一个简单的方法来测试,这是通过挂接一个SQL Server事件探查器和检查查询的数据库上执行:

SQL Server profiler

正如你所看到的只是执行一个查询。

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

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

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

+0

您是否意味着所有的数据都将从循环开始时从数据库中获取,然后这个应用程序访问内存中的每个记录? –

+0

全部'person'对象将在内存中,但是如果你访问'person'的任何子对象,它可能会在循环中执行额外的查询(因为它默认情况下是延迟加载) – Nicholas

+1

@OmidEbrahimi我已经更新了我的答案并添加了一个屏幕截图来解释发生了什么 – Nicholas

相关问题