7

使用这个问题给出的代码, OrderBy 在传递选择器函数时不会翻译成 SQL

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]

好的。

我可以理解代码编译:IQueryable继承自IEnumerable,它有一个 OrderBy 方法,Func<TModel, TValue>作为参数。

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

但幕后发生了什么?“错误的” OrderBy 方法会发生什么?没有 ?我不知道如何以及为什么......我的夜晚有光吗?

4

4 回答 4

5

因为f是委托而不是表达式,所以编译器选择IEnumerable OrderBy扩展方法而不是扩展方法IQueryable

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

当然,实际上这仍然不会真正发生,直到您开始枚举结果 - 在您的情况下,您会立即这样做,因为您通过调用ToList().

更新以回应您的评论

从引入歧义的角度来看,您的问题似乎同样是关于IQueryable/对偶是“危险的”。IEnumerable真的不是:

t.OrderBy(r => r.Field)

C# 将 lambda 视为Expression<>最重要的,因此如果t是,IQueryableIQueryable选择扩展方法。它与通过 a和重载string传递给重载方法的变量相同-将使用版本,因为它是最好的表示stringobjectstring

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

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

C# 看不到 aIQueryable了,因此将 lambda 视为 a Func<A, B>,因为那是它的次佳表示。(相当于我之前的/类比中只有一种object方法可用。stringobject

最后是你的例子:

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

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

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

我现在意识到,通过添加此编辑,我现在已经取消了下面@IanNewson 的评论 - 但我希望它提供了一个有意义的令人信服的论点:)

于 2012-08-31T20:25:15.973 回答
3

但幕后发生了什么?

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

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

它将找到Queryable.OrderByEnumerable.OrderBy目标类型匹配的扩展方法,但该Queryable.OrderBy方法不适用,因此它使用Enumerable.OrderBy

所以你可以把它想象成编译器已经将你的代码重写为:

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

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

于 2012-08-31T20:24:55.267 回答
2

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

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

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

于 2012-08-31T20:24:12.213 回答
2

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

Zoltan 的回答很有趣,而且大部分都是正确的,除了C# 将 lambda 视为Expression<>首要的 [...]

Expression<>C# 将 lambda(和任何匿名函数)视为与委托和同一委托的(表达式树)同样“接近” 。根据 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讨论的方法不是普通的实例方法。相反,一种是派生接口的扩展方法,另一种是基接口的扩展方法。但解释是相似的。

于 2012-09-02T15:09:17.007 回答