почему LINQ to SQL обманут методом расширения? что теперь? - PullRequest
4 голосов
/ 04 марта 2009

Рассмотрим мой Event класс и то, что я храню DateTime в БД как даты UTC. Я просто хочу вернуть отфильтрованный диапазон, основанный на текущей дате в определенном часовом поясе - легко, правда?

Это прекрасно работает:

IQueryable<Event> test1 = this.GetSortedEvents().Where(e => e.FinishDateTime.Date >= DateTime.UtcNow.Date);

Это также отлично работает:

IQueryable<Event> test2 = this.GetSortedEvents().Where(e => e.FinishDateTime.AddHours(3).Date >= DateTime.UtcNow.AddHours(3).Date);

.. и дополнительно соответствует моим требованиям к часовому поясу.

Итак, я думаю, что могу перенести это конкретное преобразование в этот метод расширения:

    public static DateTime RiyadhTimeFromUtc(this DateTime utcTime)
    {
        return utcTime.AddHours(3);
    }

Это НЕ работает:

IQueryable<Event> test3 = this.GetSortedEvents().Where(e => e.FinishDateTime.RiyadhTimeFromUtc().Date >= DateTime.UtcNow.RiyadhTimeFromUtc().Date);

.. и я получаю это NotSupportedException: метод 'System.DateTime RiyadhTimeFromUtc (System.DateTime)' не имеет поддерживаемого перевода в SQL.

Это явно мусор, так как компилятор успешно преобразовал его в SQL, когда идентичный код НЕ был в методе расширения.

Раньше я сталкивался с проблемой "не поддерживается перевод на SQL" с некоторыми типами, а в последнее время и с DateTime. Но мои тесты выше и эта ссылка доказывают, что метод AddHours должен поддерживаться в переводе SQL.

Если бы кто-то мог сказать мне, что я делаю здесь неправильно (или другой обходной подход к этой проблеме), я был бы очень благодарен.

Ответы [ 2 ]

9 голосов
/ 04 марта 2009

Вы должны думать об этом в терминах дерева выражений, и именно так Linq-to-SQL анализирует ваш запрос, чтобы превратить его в SQL.

При проверке дерева оно увидит объект DateTime, а затем проверит, является ли вызываемый метод одним из поддерживаемых (Add, AddHours и т. Д.), Поэтому при непосредственном использовании метода работает отлично.

Когда вы используете какой-либо другой метод расширения, он не может заглянуть внутрь этого метода, чтобы увидеть, что он делает, поскольку информация о теле этого метода отсутствует в дереве выражений, она скрыта в IL. Поэтому не имеет значения, поддерживается ли содержимое метода расширения, потому что Linq-to-SQL не может определить, что это за содержимое.

Смысл создания методов заключается в инкапсуляции и сокрытии информации, что обычно хорошо работает при разработке приложений, но, к сожалению, здесь скрывается информация от Linq-to-SQL, для которой вам нужно иметь возможность просматривать информацию.


В ответ на отредактированный вопрос - как вы это решаете? Если вы хотите сохранить вычисление даты в выражении Linq, единственное, что вы можете сделать для обеспечения эффективности запроса, - это не использовать метод расширения, а просто использовать AddHours(3) непосредственно для объекта DateTime.

Это одно из печальных ограничений Linq. Подобно многим вещам, это несколько утечка абстракции, которая, хотя и обеспечивает общий синтаксис для ряда источников, имеет различные ограничения и ограничения в отношении того, какие операции может / будет поддерживать поставщик источника (например, это будет прекрасно работать в Linq-to -Объекты, поскольку для их выполнения не нужно переводить дерево выражений).

4 голосов
/ 04 марта 2009

Похоже, что LINQ-to-SQL сможет выяснить, что RiyadhTimeFromUtc на самом деле был просто вызовом AddHours(3), ему нужно было бы провести довольно сложный анализ вашего кода. Скажем, вы написали:

public static DateTime RiyadhTimeFromUtc(this DateTime utcTime)
{
    if (Random.GetInt() < 99) {
       return utcTime.AddHours(3);
    } else {
       return utcTime.AddDays(5);
    }
}

Как бы это перевести на SQL? См. это обсуждение для получения дополнительной информации.

...