IQueryable OrderBy с Func: что происходит

С кодом, приведенным из этого вопроса 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 ответов


, потому что f является делегатом, а не выражением, компилятор выбирает IEnumerable OrderBy метод расширения вместо IQueryable один.

это означает, что все результаты извлекаются из базы данных, потому что упорядочение выполняется в памяти, как если бы это был Linq to Objects. То есть, в памяти, упорядочение может быть сделано только путем извлечения всех записей.

конечно, на самом деле это все еще не происходит, пока вы не начнете перечисление результата-что в вашем случае вы делаете сразу, потому что вы хотите-загрузите результат своим вызовом ToList().

обновление в ответ на ваш комментарий

кажется, что ваш вопрос-это IQueryable/

t.OrderBy(r => r.Field)

C# видит лямбду как Expression<> в первую очередь, так что если t is Ан IQueryable тут IQueryable выбран метод расширения. Это то же самое, что и переменная string передается перегруженному методу с string и object перегруз - у string версия будет использоваться, потому что это лучшее представление.

как указал Йеппе, это на самом деле потому, что используется непосредственный интерфейс, прежде чем унаследованные интерфейсы

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

C# не может видеть IQueryable любой более того, так относится к лямбде как Func<A, B>, потому что это следующее лучшее представление. (Эквивалент только object метод доступен в my string/object аналогию.

и, наконец, ваш пример:

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

здесь невозможно что разработчик, пишущий этот код, может ожидать, что это будет рассматриваться как выражение для компонента более низкого уровня для перевода на SQL, если разработчик принципиально не поймите разницу между делегатом и выражением. Если это так, то немного чтения решает проблему.

я не думаю, что это неразумно требовать от разработчика сделать немного чтения, прежде чем они приступят к использованию новой технологии; особенно, когда, в защиту MSDN, эта конкретная тема освещается так хорошо.

теперь я понимаю, что, добавив это редактирование, я теперь аннулировал комментарий @IanNewson ниже - но я надеюсь это дает убедительный аргумент, который имеет смысл:)


но что происходит за кулисами?

предполагая, что db.Table1 возвращает Table<Table1> компилятор:

  • проверить, является ли Table<T> есть OrderBy способ - Неа
  • проверьте, имеет ли какой-либо из базовых классов или интерфейсов, которые он реализует,OrderBy способ - Неа
  • начните смотреть на методы расширения

он найдет оба Queryable.OrderBy и Enumerable.OrderBy как методы расширения, которые соответствуют тип цели, но Queryable.OrderBy метод не применим, поэтому он использует Enumerable.OrderBy вместо.

поэтому вы можете думать об этом так, как будто компилятор переписал ваш код в:

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

теперь во время выполнения, Enumerable.OrderBy будет перебирать его источник (db.Table1) и выполнить соответствующий заказ на основе функции извлечения ключа. (Строго говоря, это тут возвратить IEnumerable<T> который будет перебирать Источник, когда его попросят первый результат.)


queryable возвращает все записи (следовательно, нет предложения WHERE в инструкции SQL), а затем Func применяется к объектам в памяти клиента через Enumerable.OrderBy. Более конкретно, вызов OrderBy разрешает Enumerable.OrderBy, потому что параметр является Func. Поэтому вы можете переписать оператор, используя синтаксис вызова статического метода, чтобы сделать его немного яснее, что происходит:

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

конечный результат заключается в том, что сортировка, указанная OrderBy, выполняется клиентом процесс, а не сервер базы данных.


этот ответ является своего рода комментарием к ответу Андраса Золтана (но это слишком долго, чтобы соответствовать формату комментариев).

ответ Золтана интересен и в основном правильный, за исключением фразы C# видит лямбду как Expression<> в первую очередь [...].

C# видит лямбду (и любую анонимную функцию) как одинаково "близкую" к делегату и 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 рассматриваемые методы не являются обычными методами экземпляра. Вместо этого один является методом расширения для производного интерфейса, а другой-методом расширения для базового интерфейса. Но объяснение аналогично.