Лямбды с захваченными переменными

Рассмотрим следующую строку кода:

private void DoThis() {
    int i = 5;
    var repo = new ReportsRepository<RptCriteriaHint>();

    // This does NOT work
    var query1 = repo.Find(x => x.CriteriaTypeID == i).ToList<RptCriteriaHint>();      

    // This DOES work
    var query1 = repo.Find(x => x.CriteriaTypeID == 5).ToList<RptCriteriaHint>();    
}

Поэтому, когда я вставляю фактическое число в лямбда-функцию, она работает нормально. Когда я использую захваченную переменную в выражении, возвращается следующая ошибка:

No mapping exists from object type ReportBuilder.Reporter+<>c__DisplayClass0 to a known managed provider native type.

Почему? Как я могу это исправить?

Думаю, нет. Я запомню это на будущее ...

Barry Kelly 12.12.2008 13:13
Переменные, типы данных и операторы в Python
Переменные, типы данных и операторы в Python
В Python переменные используются как место для хранения значений. Пример переменной формы:
2
1
763
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

С технической точки зрения, правильный способ исправить это - использовать фреймворк, принимающий дерево выражений из лямбда-выражения для оценки ссылки i; другими словами, это ограничение платформы LINQ для какой-то конкретной платформы. В настоящее время он пытается интерпретировать i как членский доступ к некоторому типу, известному ему (поставщику) из базы данных. Из-за того, как работает захват лямбда-переменных, локальная переменная i на самом деле является полем в скрытом классе с забавным именем, которое провайдер не распознает.

Итак, это проблема фреймворка.

Если вам действительно нужно обойтись, вы можете создать выражение вручную, например:

ParameterExpression x = Expression.Parameter(typeof(RptCriteriaHint), "x");
var query = repo.Find(
    Expression.Lambda<Func<RptCriteriaHint,bool>>(
        Expression.Equal(
            Expression.MakeMemberAccess(
                x,
                typeof(RptCriteriaHint).GetProperty("CriteriaTypeID")),
            Expression.Constant(i)),
        x)).ToList();

... но это всего лишь мазохизм.

Ваш комментарий к этой записи побуждает меня к дальнейшим объяснениям.

Лямбда-выражения можно преобразовать в один из двух типов: делегат с правильной подписью или Expression<TDelegate> с правильной подписью. LINQ для внешних баз данных (в отличие от любых запросов в памяти) работает с использованием второго типа преобразования.

Компилятор преобразует лямбда-выражения в деревья выражений, грубо говоря, следующим образом:

  1. Синтаксическое дерево анализируется компилятором - это происходит для всего кода.
  2. Синтаксическое дерево переписывается с учетом захвата переменных. Захват переменных такой же, как в обычном делегате или лямбда-выражении - поэтому создаются классы отображения, а захваченные локальные переменные перемещаются в них (это то же поведение, что и захват переменных в анонимных делегатах C# 2.0).
  3. Новое синтаксическое дерево преобразуется в серию вызовов класса Expression, так что во время выполнения создается дерево объектов, точно представляющее проанализированный текст.

Предполагается, что LINQ для внешних источников данных берет это дерево выражений и интерпретирует его на предмет его семантического содержания, а также интерпретирует символические выражения внутри дерева как относящиеся либо к вещам, специфичным для его контекста (например, столбцы в БД), либо как непосредственные значения для преобразования. Обычно System.Reflection используется для поиска специфичных для платформы атрибутов, направляющих это преобразование.

Однако похоже, что SubSonic неправильно обрабатывает символические ссылки, для которых он не может найти соответствия предметной области; вместо того, чтобы оценивать символические отсылки, это просто пантерация. Таким образом, это проблема SubSonic.

Фреймворк - SubSonic 3.0. Но не следует ли оценивать выражение сравнения перед передачей в метод .Find? Видите, как выражение сравнения представляет собой обычный код на C#?

AngryHacker 04.12.2008 05:08

Нет, выражение сравнения нет оценивается перед передачей.

Barry Kelly 04.12.2008 05:16

Спасибо, я свяжусь с автором SubSonic и выясню, почему он не обрабатывает захваченные переменные.

AngryHacker 04.12.2008 05:18

Я обновил свой пост. Фактически, дерево выражений, которое я вручную создаю выше, является приближением того, что происходит для вашего рабочего случая, за исключением того, что вместо Expression.Constant (i) компилятор будет генерировать Expression.Constant (5).

Barry Kelly 04.12.2008 05:32

Подтвержденный. Это известная проблема в SubSonic 3, над которой мы будем работать на следующей неделе.

AngryHacker 04.12.2008 19:29

Обновлять. SubSonic 3 Alpha теперь поддерживает захваченные переменные.

AngryHacker 12.12.2008 00:14

Другие вопросы по теме