Linq to Entities OrderBy在传递keySelector时更早地评估查询

时间:2013-09-23 13:39:44

标签: c# .net linq entity-framework

我正在使用Entity Framework实现服务器端分页,并具有以下代码

 DbQuery<T> query = Context.Set<T>();

 query = IncludeNavigationProperties(query, includedProperties);

 var result =  query.OrderBy(arg => arg.DatabaseId)
                    .Skip((pageNumber - 1)*pageSize)
                    .Take(pageSize).ToList();

生成一个只查询必要数据的SQL(使用SQL Server Profiler检查)

SELECT TOP (21) 
[Extent1].[DatabaseId] AS [DatabaseId], 
...[other props here]...
FROM ( SELECT [Extent1].[DatabaseId] AS [DatabaseId], ...[other props here]..., row_number() OVER (ORDER BY [Extent1].[DatabaseId] ASC) AS [row_number]
    FROM [dbo].[Table] AS [Extent1]
)  AS [Extent1]
WHERE [Extent1].[row_number] > 84
ORDER BY [Extent1].[DatabaseId] ASC

然后我决定在更多场景中重用此方法,并将keySelector作为外部变量传递:

 DbQuery<T> query = Context.Set<T>();

 query = IncludeNavigationProperties(query, includedProperties);

 var result =  query.OrderBy(keySelector)
                    .Skip((pageNumber - 1)*pageSize)
                    .Take(pageSize).ToList();

其中

Func<T, int> keySelector = arg => arg.DatabaseId;

但它突然生成以下SQL查询:

SELECT 
[Extent1].[DatabaseId] AS [DatabaseId], 
...[other props here]...
FROM [dbo].[Table] AS [Extent1]

根据我的理解,从表中查询所有数据,然后在服务器上处理它。

所以,我有两个问题:

  1. 为什么查询会更改?
  2. 如何修复它(能够改变keySelector并仅查询必要的数据)?

1 个答案:

答案 0 :(得分:2)

DbQuery<T>来自IQueryable<T>IEnumerable<T>类。这两个类都提供OrderBy方法,但有一点不同:OrderBy上的IEnumerable获得Func<T1,T2>,而OrderBy上的IQueriable获得Expression<Func<T1,T2>>参数。当您将keyselector作为Func<T1,T2>对象传递给OrderBy方法时,您告诉编译器:嘿!请使用OrderBy上定义的IEnumerable方法。换句话说,您的DbQuery<T>对象已投放到IEnumerable<T>而非IQueriable<T>.这就是为什么所有数据都被提取到客户端,并且更进一步的操作在内存中完成。

要解决此问题,请将keyselector的类型从Func<T1,TKey>更改为Expression <Func<T1,TKey>>

public IQueriable<T> YourMethodName<T, TKey>(Expression<Func<T,TKey>> keyselector)
{
    DbQuery<T> query = Context.Set<T>();

     query = IncludeNavigationProperties(query, includedProperties);

     var result =  query.OrderBy(keySelector)
                        .Skip((pageNumber - 1)*pageSize)
                        .Take(pageSize).ToList();
     return result;
}