IQueryable OrderBy with Func <tmodel,tvalue =“”>:发生了什么</tmodel,>

时间:2012-08-31 20:21:37

标签: c# linq-to-entities

使用此问题提供的代码 OrderBy is not translated into SQL when passing a selector function

Func<Table1, string> f = x => x.Name;
var t = db.Table1.OrderBy(f).ToList();

翻译的SQL是:

SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[Name] AS [Name]
FROM [dbo].[Table1] AS [Extent1]

行。

我可以理解代码编译:{{1​​}}继承自IQueryable,它有一个以IEnumerable为参数的OrderBy方法。

我可以理解,ORDER BY子句不是在SQL中生成的,因为我们没有传递Func<TModel, TValue>作为OrderBy参数(IQueryable的参数)

但是场景背后会发生什么? &#34;错误&#34;会发生什么? OrderBy方法?没有 ?我无法看到如何以及为什么......我夜晚有什么亮光吗?

4 个答案:

答案 0 :(得分:5)

由于f是委托而非表达式,因此编译器选择IEnumerable OrderBy扩展方法而不是IQueryable方法。

这意味着所有结果都是从数据库中获取的,因为然后在内存中完成排序,就像它是Linq to Objects一样。也就是说,在内存中,只能通过获取所有记录来完成排序。

当然,实际上直到您开始枚举结果时才会发生这种情况 - 在您的情况下,您会立即执行此操作,因为您通过调用ToList()来急切加载结果。

更新以回复您的评论

从引入歧义的角度来看,你的问题似乎与IQueryable / IEnumerable二元性是“危险的”一样多。它真的不是:

t.OrderBy(r => r.Field)

C#首先将lambda视为Expression<>,因此如果tIQueryable,则会选择IQueryable扩展方法。它与传递给stringstring重载的重载方法的object变量相同 - 将使用string版本,因为它是最好的表示

正如Jeppe指出的那样,实际上是因为在继承接口之前使用了直接接口

t.AsEnumerable().OrderBy(r => r.Field)

C#再也看不到IQueryable,所以将lambda视为Func<A, B>,因为这是次佳表示。 (相当于我之前object / string类比中仅提供object方法。

然后最后你的例子:

Func<t, string> f = r => r.Field;
t.OrderBy(f);

没有办法编写此代码的开发人员可以期望将其视为较低级别组件转换为SQL的表达式,除非开发人员从根本上没有“理解委托和表达之间的区别。如果是这种情况,那么一点点阅读可以解决问题。

我认为在开始使用新技术之前要求开发人员进行一些阅读是不合理的。特别是在MSDN的辩护中,这个特定的主题被很好地覆盖了。

我现在意识到,通过添加此编辑,我现在已经使@IanNewson的注释无效 - 但我希望它提供了一个有意义的引人注目的论点:)

答案 1 :(得分:3)

  

但幕后发生了什么?

假设db.Table1返回Table<Table1>,编译器将:

  • 检查Table<T>是否有OrderBy方法 - nope
  • 检查它实现的任何基类或接口是否具有OrderBy方法 - nope
  • 开始查看扩展方法

它会发现Queryable.OrderByEnumerable.OrderBy作为与目标类型匹配的扩展方法,但Queryable.OrderBy方法不适用,因此它使用Enumerable.OrderBy代替。

因此,您可以将其视为编译器已将代码重写为:

List<Table1> t = Enumerable.ToList(Enumerable.OrderBy(db.Table1, f));

现在在执行时,Enumerable.OrderBy将迭代其源(db.Table1)并根据密钥提取功能执行适当的排序。 (严格来说,它将立即 返回IEnumerable<T>,当它被要求提供第一个结果时,它会迭代源。)

答案 2 :(得分:2)

queryable返回所有记录(因此SQL语句中没有WHERE子句),然后通过Enumerable.OrderBy将Func应用于客户端内存中的对象。更具体地说,OrderBy调用解析为Enumerable.OrderBy,因为参数是Func。因此,您可以使用静态方法调用语法重写语句,以使其更加清晰:

Func<Table1, string> f = x => x.Name; 
var t = Enumerable.OrderBy(db.Table1, f).ToList(); 

最终结果是OrderBy指定的排序由客户端进程而不是数据库服务器完成。

答案 3 :(得分:2)

这个答案是对Andras Zoltan的答案的一种评论(但是这个评论格式太长了。)

Zoltan的回答很有意思,而且大多数都是正确的,除了 C#这个词首先将lambda视为Expression<>,最重要的是[...]

C#将lambda(和任何匿名函数)视为与委托和同一委托的Expression<>(表达式树)同等“接近”。根据C#规范,它们都不是“更好的转换目标”

请考虑以下代码:

class C
{
    public void Overloaded(Expression<Func<int, int>> e)
    {
        Console.WriteLine("expression tree");
    }
    public void Overloaded(Func<int, int> d)
    {
        Console.WriteLine("delegate");
    }
}

然后:

var c = new C();
c.Overloaded(i => i + 1);   // will not compile! "The call is ambiguous ..."

因此它与IQueryable<>一起使用的原因是其他原因。直接接口类型定义的方法优于基接口中定义的方法。

为了说明,请将上面的代码更改为:

interface IBase
{
    void Overloaded(Expression<Func<int, int>> e);
}
interface IDerived : IBase
{
    void Overloaded(Func<int, int> d);
}
class C : IDerived
{
    public void Overloaded(Expression<Func<int, int>> e)
    {
        Console.WriteLine("expression tree");
    }
    public void Overloaded(Func<int, int> d)
    {
        Console.WriteLine("delegate");
    }
}

然后:

IDerived x = new C();
x.Overloaded(i => i + 1);  // compiles! At runtime, writes "delegate" to the console

如您所见,选择IDerived中定义的成员,而不是IBase中定义的成员。请注意,我改变了这种情况(与IQueryable<>相比),因此在我的示例中,委托重载在最派生的接口中定义,因此优先于表达式树重载。

注意:在IQueryable<>情况下,所讨论的OrderBy方法不是普通的实例方法。相反,一个是派生接口的扩展方法,另一个是基接口的扩展方法。但解释是类似的。