Лямбды с захваченными переменными - PullRequest
2 голосов
/ 04 декабря 2008

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

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>();    
}

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

Нет сопоставления для типа объекта ReportBuilder.Reporter + <> c__DisplayClass0 известному управляемому провайдеру тип.

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

1 Ответ

9 голосов
/ 04 декабря 2008

Технически, правильный способ исправить это - это фреймворк, который принимает дерево выражений из вашей лямбды для оценки ссылки 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.

...