«Лямбда-выражение с телом оператора не может быть преобразовано в дерево выражений»

181

При использовании EntityFramework я получаю ошибку " A lambda expression with a statement body cannot be converted to an expression tree" при попытке скомпилировать следующий код:

Obj[] myArray = objects.Select(o =>
{
    var someLocalVar = o.someVar;

    return new Obj() { 
    Var1 = someLocalVar,
    Var2 = o.var2 };
}).ToArray();

Я не знаю, что означает ошибка, и больше всего, как ее исправить. Любая помощь?

pistacchio
источник
6
попытаться преобразовать в список, как это. objects.List (). Выбрать (...
Нельсон Элдоро

Ответы:

114

Является ли objectsконтекст базы данных Linq-To-SQL? В этом случае вы можете использовать только простые выражения справа от оператора =>. Причина в том, что эти выражения не выполняются, а преобразуются в SQL для выполнения в базе данных. Попробуй это

Arr[] myArray = objects.Select(o => new Obj() { 
    Var1 = o.someVar,
    Var2 = o.var2 
}).ToArray();
Тим Роджерс
источник
102

Вы можете использовать тело оператора в выражении lamba для коллекций IEnumerable . Попробуй это:

Obj[] myArray = objects.AsEnumerable().Select(o =>
{
    var someLocalVar = o.someVar;

    return new Obj() 
    { 
        Var1 = someLocalVar,
        Var2 = o.var2 
    };
}).ToArray();

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

Амир Овеиси
источник
4
+1 Мне это нравится! Добавив AsEnumerable()маски, моя проблема исчезнет!
Джоэл
5
Это реальное решение, принятый ответ трудно применить в некоторых случаях
Ferran Salguero
15
Нет, это не настоящий ответ. Это сделает ваш запрос выполненным на стороне клиента. За подробностями обращайтесь к этому вопросу: stackoverflow.com/questions/33375998/…
Люк Во
1
@DatVM это зависит от того, что вы собираетесь делать. это не всегда может быть правильным выбором и, конечно, не всегда может быть неправильным выбором.
Амир Овеиси
3
Хотя я согласен с вами, ОП заявил, что он использует EntityFramework. В большинстве случаев при работе с EF вы хотите, чтобы сторона базы данных выполняла как можно больше работы. Было бы неплохо, если бы вы отметили случай в своем ответе.
Люк Во
39

Это означает, что вы не можете использовать лямбда-выражения с «телом оператора» (т. Е. Лямбда-выражениями, в которых используются фигурные скобки) в тех местах, где лямбда-выражения необходимо преобразовать в дерево выражений (что, например, имеет место при использовании linq2sql) ,

sepp2k
источник
37
Вы ... слегка перефразировали ошибку. Ответ @Tim Rogers был намного лучше
vbullinger
2
@vbullinger вы правы в некоторой степени, но в более общем смысле (вне контекста linq-to-sql) это более прямой ответ. Это помогло мне с ошибкой
AutoMapper
1
vbullinger: Это помогло мне, хотя.
Поль
7

Не зная больше о том, что вы делаете (Linq2Objects, Linq2Entities, Linq2Sql?), Это должно заставить его работать:

Arr[] myArray = objects.AsEnumerable().Select(o => {
    var someLocalVar = o.someVar;

    return new Obj() { 
        Var1 = someLocalVar,
        Var2 = o.var2 
    }; 
}).ToArray();
расточитель
источник
11
Это заставляет запрашиваемое оценить.
Smartcaveman
Однако в этом случае это нормально, потому что он вызывает ToArray () сразу после этого.
Smartcaveman
2
не обязательно - кто знает, насколько велико «о»? он может иметь 50 свойств, когда все, что мы хотим, - 2.
kdawg
1
Когда я использую эту технику, мне нравится выбирать поля, которые я буду использовать, в анонимный тип перед вызовом.AsEnumerable()
Блейк Митчелл
4

Используйте эту перегрузку select:

Obj[] myArray = objects.Select(new Func<Obj,Obj>( o =>
{
    var someLocalVar = o.someVar;

    return new Obj() 
    { 
       Var1 = someLocalVar,
       Var2 = o.var2 
    };
})).ToArray();
Мохсен
источник
Это работает для меня, но при использовании с Entity Framework это решение будет препятствовать загрузке dbcontext всех строк в память в первую очередь, как это делает AsEnumerable ()?
парламент
2
@par Parliament: для предотвращения загрузки всех строк в память, вы должны использовать Expression<Func<Obj,Obj>>.
Мохсен
4

Возвращаемый объект LINQ to SQL реализовывал IQueryableинтерфейс. Поэтому для Selectпараметра-предиката метода следует указывать только одно лямбда-выражение без тела.

Это связано с тем, что LINQ для кода SQL не выполняется внутри программы, а не на удаленной стороне, такой как сервер SQL или другие. Этот тип выполнения отложенной загрузки был достигнут путем реализации IQueryable, где его ожидаемый делегат обернут в класс типа Expression, как показано ниже.

Expression<Func<TParam,TResult>>

Дерево выражений не поддерживает лямбда-выражения с телом и его единственная поддержка лямбда-выражений, например var id = cols.Select( col => col.id );

Поэтому, если вы попробуете следующий код не будет работать.

Expression<Func<int,int>> function = x => {
    return x * 2;
}

Следующее будет работать в соответствии с ожиданиями.

Expression<Func<int,int>> function = x => x * 2;
Азри Джамиль
источник
2

Это означает, что лямбда-выражение типа, TDelegateкоторое содержит, ([parameters]) => { some code };не может быть преобразовано вExpression<TDelegate> . Это правило.

Упростите ваш запрос. Тот, который вы предоставили, может быть переписан как следующий и будет скомпилирован:

Arr[] myArray = objects.Select(o => new Obj()
                {
                   Var1 = o.someVar,
                   Var2 = o.var2
                } ).ToArray();
smartcaveman
источник
1

Является Arrбазовым типом Obj? Существует ли класс Obj? Ваш код будет работать, только если Arr является базовым типом Obj. Вы можете попробовать это вместо этого:

Obj[] myArray = objects.Select(o =>
{
    var someLocalVar = o.someVar;

    return new Obj() 
    { 
       Var1 = someLocalVar,
       Var2 = o.var2 
    };
}).ToArray();
Атанас Корчев
источник
1

В вашем конкретном случае тело предназначено для создания переменной, и переключение на IEnumerableзаставит все операции обрабатываться на стороне клиента. Я предлагаю следующее решение.

Obj[] myArray = objects
.Select(o => new
{
    SomeLocalVar = o.someVar, // You can even use any LINQ statement here
    Info = o,
}).Select(o => new Obj()
{
    Var1 = o.SomeLocalVar,
    Var2 = o.Info.var2,
    Var3 = o.SomeLocalVar.SubValue1,
    Var4 = o.SomeLocalVar.SubValue2,
}).ToArray();

Изменить: Переименовать для C # Coding Convention

Люк Во
источник