将表达式与LINQ一起使用时,SQL查询的生成无效



考虑以下代码,其中dbContext是SQL Server数据库上下文,ExamplesDbSet:

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.TakeIEnumerable引用,在从数据库中获取所有数据后,Take方法的执行将在内存中进行。

"this.dbContext.Examples"确实获得了所有数据,然后是Enumerable。取filter并从中取前5个。

最新更新