考虑以下代码,其中dbContext
是SQL Server数据库上下文,Examples
是DbSet
:
this.dbContext.Examples.Take(5).ToList();
Enumerable.Take(this.dbContext.Examples, 5).ToList();
第一行按预期工作,并以以下方式转换为SQL:
SELECT TOP(5) * FROM Examples
然而,第二行首先获取所有行,然后应用Take
运算符。为什么?
由于我使用表达式来构建动态lambda,所以我必须使用第二种方法(Enumerable.Take
(:
var call = Expression.Call(
typeof(Enumerable),
"Take",
new[]{ typeof(Examples) },
contextParam,
Expression.Constant(5)
);
不幸的是,第一种方法在处理表达式时不起作用,并且程序的当前架构迫使我动态地构建lambda。
为什么第二种方法获取所有行,如何防止它在表达式中有效使用?
您没有调用相同的方法。第一行调用的是Queryable.Take
,而不是Enumerable.Take
。
由于DbSet
同时实现IQueryable<>
和IEnumerable<>
,但IQueryable<>
实现IEnumerable<>
,因此编译器将IQueryable<>
视为更具体的类型。因此,当它解析要调用的Take
扩展方法时,它会确定Queryable.Take(...)
是正确的,因为它需要IQueryable<>
作为第一个参数。
这一点很重要,因为IQueryable<>
接口允许将LINQ查询构建为表达式树,并将其求值为SQL。当您切换到将IQueryable<>
视为IEnumerable<>
时,您就失去了这种行为,并切换到只能迭代之前构建的任何查询的结果。
试试这个:
Queryable.Take(this.dbContext.Examples, 5).ToList();
或者这个:
var call = Expression.Call(
typeof(Queryable),
"Take",
new[]{ typeof(Examples) },
contextParam,
Expression.Constant(5)
);
它之所以有效,是因为在第一条语句中
dbContext.Examples.Take(5).ToList();
您正在IQueryable
接口上调用.Take(5)
,LINQ到SQL提供程序可以在该接口上对数据库执行正确的SQL语句。
如果需要在数据库端进行查询,则必须在IQueryable
接口实例上构造查询。
Enumerable.Take
是IEnumerable
引用,在从数据库中获取所有数据后,Take
方法的执行将在内存中进行。
"this.dbContext.Examples"确实获得了所有数据,然后是Enumerable。取filter并从中取前5个。