"Uma expressão lambda com um corpo de instrução não pode ser convertida em uma árvore de expressão"

181

Ao usar o EntityFramework , recebo o erro " A lambda expression with a statement body cannot be converted to an expression tree" ao tentar compilar o seguinte código:

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

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

Não sei o que significa o erro e, acima de tudo, como corrigi-lo. Qualquer ajuda?

pistacchio
fonte
6
tente converter para a lista como esta. ()
nelson eldoro 10/10

Respostas:

114

É objectsum contexto de banco de dados Linq-To-SQL? Nesse caso, você pode usar apenas expressões simples à direita do operador =>. O motivo é que essas expressões não são executadas, mas são convertidas em SQL para serem executadas no banco de dados. Tente isto

Arr[] myArray = objects.Select(o => new Obj() { 
    Var1 = o.someVar,
    Var2 = o.var2 
}).ToArray();
Tim Rogers
fonte
102

Você pode usar o corpo da instrução na expressão lamba para coleções IEnumerable . tente este:

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

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

Aviso:
pense com cuidado ao usar esse método, pois dessa forma, você terá todos os resultados da consulta na memória, que podem ter efeitos colaterais indesejados no restante do seu código.

Amir Oveisi
fonte
4
+1 Eu gosto disso! Adicionando AsEnumerable()máscaras meu problema vai embora!
Joel
5
Esta é a solução real, a resposta aceita é difícil de aplicar em alguns casos
Ferran Salguero
15
Não, essa não é a resposta real. Isso tornaria sua consulta executada no lado do cliente. Consulte esta pergunta para obter detalhes: stackoverflow.com/questions/33375998/…
Luke Vo
1
@DatVM, depende do que você vai fazer. isso nem sempre pode ser a escolha certa e, claro, nem sempre pode ser a escolha errada.
Amir Oveisi 28/10/2015
3
Embora eu concorde com você, o OP afirmou que ele estava usando o EntityFramework. Na maioria dos casos, ao trabalhar com o EF, você deseja que o lado do banco de dados faça o máximo de trabalho possível. Seria bom se você notar o caso na sua resposta.
Luke Vo
39

Isso significa que você não pode usar expressões lambda com um "corpo de instrução" (ou seja, expressões lambda que usam chaves) em locais onde a expressão lambda precisa ser convertida em uma árvore de expressão (que é o caso ao usar linq2sql) .

sepp2k
fonte
37
Você ... reformulou ligeiramente o erro. Resposta @ Tim Rogers' era muito melhor
vbullinger
2
@vbullinger você está certo até certo ponto, mas em um sentido mais geral (fora do contexto do linq-sql), essa é uma resposta mais direta. Isso me ajudou com um erro do AutoMapper
mlhDev 30/12/2014
1
vbullinger: Mas isso me ajudou.
Paulo
7

Sem saber mais sobre o que você está fazendo (Linq2Objects, Linq2Entities, Linq2Sql?), Isso deve fazer com que funcione:

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

    return new Obj() { 
        Var1 = someLocalVar,
        Var2 = o.var2 
    }; 
}).ToArray();
gastador
fonte
11
Isso força o questionável a avaliar.
smartcaveman
No entanto, nessa circunstância, tudo bem, porque ele chama ToArray () logo depois de qualquer maneira.
smartcaveman
2
não necessariamente - quem sabe quão grande é o "o"? ele poderia ter 50 propriedades quando todos nós queremos são 2.
kdawg
1
Ao usar esta técnica, gosto de selecionar os campos que utilizarei em um tipo anônimo antes de ligar.AsEnumerable()
Blake Mitchell
4

Use esta sobrecarga de seleção:

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

    return new Obj() 
    { 
       Var1 = someLocalVar,
       Var2 = o.var2 
    };
})).ToArray();
Mohsen
fonte
Isso funciona para mim, mas quando usada com o Entity Framework, essa solução impediria que o dbcontext carregasse todas as linhas na memória primeiro, como AsEnumerable () faria?
parlamento
2
@parl Parliament: Para evitar carregar todas as linhas na memória, você deve usar Expression<Func<Obj,Obj>>.
Mohsen 25/11
4

O objeto de retorno LINQ to SQL estava implementando a IQueryableinterface. Portanto, para o Selectparâmetro predicado do método, você deve fornecer apenas uma expressão lambda sem corpo.

Isso ocorre porque o código LINQ for SQL não é executado dentro do programa e não no lado remoto, como o SQL Server ou outros. Esse tipo de execução de carregamento lento foi conseguido implementando IQueryable onde seu representante esperado está sendo agrupado na classe Tipo de expressão, como abaixo.

Expression<Func<TParam,TResult>>

A árvore de expressão não suporta expressão lambda com body e seu único suporte a expressão lambda de linha única como var id = cols.Select( col => col.id );

Portanto, se você tentar o seguinte código não funcionará.

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

O seguinte funcionará conforme o esperado.

Expression<Func<int,int>> function = x => x * 2;
Azri Jamil
fonte
2

Isso significa que uma expressão Lambda do tipo TDelegateque contém a ([parameters]) => { some code };não pode ser convertida em um Expression<TDelegate>. É a regra.

Simplifique sua consulta. O que você forneceu pode ser reescrito da seguinte maneira e será compilado:

Arr[] myArray = objects.Select(o => new Obj()
                {
                   Var1 = o.someVar,
                   Var2 = o.var2
                } ).ToArray();
smartcaveman
fonte
1

É Arrum tipo de base Obj? A classe Obj existe? Seu código funcionaria apenas se Arr fosse um tipo base de Obj. Você pode tentar isso:

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

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

Para o seu caso específico, o corpo é para criar uma variável e, alternando para IEnumerableforçará todas as operações a serem processadas no lado do cliente, proponho a seguinte solução.

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

Editar: Renomear para Convenção de Codificação C #

Luke Vo
fonte