Linq 为什么实体框架是';s AsEnumerable()是否从服务器下载所有数据?
当使用Linq 为什么实体框架是';s AsEnumerable()是否从服务器下载所有数据?,linq,entity-framework,Linq,Entity Framework,当使用AsEnumerable()时,EF下载所有结果行的原因是什么 我的意思是这个代码: context.Logs.AsEnumerable().Where(x => x.Id % 2 == 0).Take(100).ToList(); 将从表中下载所有行,然后将任何行传递到Where()方法,表中可能有数百万行 我想让它做的是,只下载足够收集100行满足Id%2==0条件的数据(很可能只有大约200行) EF不能像使用普通ADO.NET那样使用Read()方法SqlDataReade
AsEnumerable()
时,EF下载所有结果行的原因是什么
我的意思是这个代码:
context.Logs.AsEnumerable().Where(x => x.Id % 2 == 0).Take(100).ToList();
将从表中下载所有行,然后将任何行传递到Where()
方法,表中可能有数百万行
我想让它做的是,只下载足够收集100行满足Id%2==0
条件的数据(很可能只有大约200行)
EF不能像使用普通ADO.NET那样使用Read()
方法SqlDataReader
按需加载行并节省时间和带宽吗
我想它不这样工作是有原因的,我希望听到一个很好的论据来支持这个设计决策
注意:这是一个完全人为设计的示例,我知道通常不应该以这种方式使用EF,但我在一些现有代码中发现了这一点,我只是惊讶于我的假设被证明是不正确的。AsEnumerable()
急切地加载DbSet
日志
你可能想要像这样的东西
context.Logs.Where(x => x.Id % 2 == 0).AsEnumerable();
这里的想法是,在从数据库实际加载集合之前,先对集合应用谓词过滤器
英孚支持LINQ世界中令人印象深刻的一个子集。它会在幕后将漂亮的LINQ查询转换为SQL表达式。我以前遇到过这个问题。 在调用linq函数之前不会执行上下文命令,因为您已经完成了
context.Logs.AsEnumerable()
它假定您已经完成了查询,因此编译并返回所有行。
如果将此更改为:
context.Logs.Where(x => x.Id % 2 == 0).AsEnumerable()
它将编译一个SQL语句,只获取id为modular 2的行。
如果你这样做了,也是一样
context.Logs.Where(x => x.Id % 2 == 0).Take(100).ToList();
这将创造一个声明,将获得前100名
我希望这会有所帮助。实体框架和Linq使用延迟加载。这意味着(除其他外)他们在需要枚举结果之前不会运行查询:例如使用
ToList()
或AsEnumerable()
,或者如果结果用作枚举器(例如在foreach
中)
相反,它使用谓词构建查询,并返回IQueryable
对象,以在实际返回结果之前进一步“预过滤”结果。例如,您可以找到更多信息。实体框架将根据您传递的谓词实际构建SQL查询
在您的示例中:
context.Logs.AsEnumerable().Where(x => x.Id % 2 == 0).Take(100).ToList();
从上下文中的Logs表中,它获取全部,返回一个带有结果的IEnumerable
,然后过滤结果,取前100个,然后将结果列为列表
另一方面,只需删除AsEnumerable
即可解决您的问题:
context.Logs.Where(x => x.Id % 2 == 0).Take(100).ToList();
在这里,它将在结果上构建一个查询/过滤器,然后仅在执行ToList()
之后,查询数据库
这还意味着您可以动态构建复杂查询,而无需在DB上实际运行它,直到结束,例如:
var logs = context.Logs.Where(a); // first filter
if (something) {
logs = logs.Where(b); // second filter
}
var results = logs.Take(100).ToList(); // only here is the query actually executed
更新
正如你在评论中提到的,你似乎已经知道我刚才写了什么,只是想问一个原因
它甚至更简单:因为AsEnumerable
将结果强制转换为另一种类型(在本例中,aIQueryable
转换为IEnumerable
),它必须首先转换所有结果行,因此必须首先获取数据。在本例中,它基本上是一个ToList
。LinQ to实体在进入枚举之前有一个由所有LinQ方法形成的存储表达式
当您使用AsEnumerable()和Where()时,如下所示:
Lambda (=>)
Parameters
Variable: x
Body
Equals (==)
Modulo (%)
PropertyAccess (.)
Variable: x
Property: Id
Constant: 2
Constant: 0
SELECT *
FROM "Logs"
WHERE "Logs"."Id" % 2 = 0;
SELECT *
FROM "Logs"
WHERE "Logs"."Id" % 2 = 0
LIMIT 100;
context.Logs.Where(…).AsEnumerable()
Where()知道前一个链调用有一个存储表达式,因此他将谓词附加到该表达式以进行延迟加载
如果调用此函数,则调用位置的重载不同:
context.Logs.AsEnumerable().Where(...)
在这里,Where()只知道他以前的方法是枚举(它可以是任何类型的“可枚举”集合),他可以应用条件的唯一方法是使用DbSet类的IEnumerable实现在集合上迭代,必须首先从数据库中检索记录。我认为您不应该使用此选项:
context.Logs.AsEnumerable().Where(x => x.Id % 2 == 0).Take(100).ToList();
正确的做法是:
context.Logs.AsQueryable().Where(x => x.Id % 2 == 0).Take(100).ToList();
请在此处解释:
简短回答:不同行为的原因是,当您直接使用IQueryable
时,可以为整个LINQ查询形成一个SQL查询;但是当您使用IEnumerable
时,必须加载整个数据表
<强>长回答:< /强>考虑下面的代码。
context.Logs.Where(x => x.Id % 2 == 0)
context.log
的类型为IQueryable
IQueryable。其中
将一个表达式
作为谓词。表达式
表示一个抽象语法树;也就是说,它不仅仅是您可以运行的代码。可以将其视为在运行时在内存中表示,如下所示:
Lambda (=>)
Parameters
Variable: x
Body
Equals (==)
Modulo (%)
PropertyAccess (.)
Variable: x
Property: Id
Constant: 2
Constant: 0
SELECT *
FROM "Logs"
WHERE "Logs"."Id" % 2 = 0;
SELECT *
FROM "Logs"
WHERE "Logs"."Id" % 2 = 0
LIMIT 100;
LINQ to Entities引擎可以采用context.Logs.Where(x=>x.Id%2==0)
并机械地将其转换为如下所示的SQL查询:
Lambda (=>)
Parameters
Variable: x
Body
Equals (==)
Modulo (%)
PropertyAccess (.)
Variable: x
Property: Id
Constant: 2
Constant: 0
SELECT *
FROM "Logs"
WHERE "Logs"."Id" % 2 = 0;
SELECT *
FROM "Logs"
WHERE "Logs"."Id" % 2 = 0
LIMIT 100;
如果将代码更改为context.Logs.Where(x=>x.Id%2==0).Take(100)
,则SQL查询将变成如下所示:
Lambda (=>)
Parameters
Variable: x
Body
Equals (==)
Modulo (%)
PropertyAccess (.)
Variable: x
Property: Id
Constant: 2
Constant: 0
SELECT *
FROM "Logs"
WHERE "Logs"."Id" % 2 = 0;
SELECT *
FROM "Logs"
WHERE "Logs"."Id" % 2 = 0
LIMIT 100;
这完全是因为IQueryable
上的LINQ扩展方法使用Expression
而不仅仅是Func
现在考虑<代码>上下文.Logas.asMeaveRable()。其中(x= > x.id % 2=0)。
IEnumerable.Where
扩展方法将Func
作为谓词。这只是可运行的代码。无法对其进行分析以确定其结构;它不能用于形成SQL查询。很明显,您知道原因