„Wyrażenia lambda z treścią instrukcji nie można przekonwertować na drzewo wyrażeń”

181

Podczas korzystania z EntityFrameworkA lambda expression with a statement body cannot be converted to an expression tree pojawia się błąd „ ” podczas próby skompilowania następującego kodu:

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

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

Nie wiem, co oznacza błąd, a przede wszystkim jak go naprawić. Jakaś pomoc?

pistacchio
źródło
6
spróbuj przekonwertować na taką listę. objects.List (). Wybierz (...
nelson eldoro 10.10.2013

Odpowiedzi:

114

Czy objectskontekst bazy danych Linq-To-SQL? W takim przypadku możesz używać prostych wyrażeń po prawej stronie operatora =>. Powodem jest to, że te wyrażenia nie są wykonywane, ale są konwertowane na SQL, aby były wykonywane na bazie danych. Spróbuj tego

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

W wyrażeniach lamba można używać treści instrukcji dla kolekcji IEnumerable . Spróbuj tego:

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

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

Uwaga:
Korzystając z tej metody, zastanów się ostrożnie, ponieważ w ten sposób wszystkie zapytania zostaną zapisane w pamięci, co może mieć niepożądane skutki uboczne w pozostałej części kodu.

Amir Oveisi
źródło
4
+1 Podoba mi się! Dodając AsEnumerable()maski, mój problem zniknął!
Joel
5
To jest prawdziwe rozwiązanie, w niektórych przypadkach trudno jest zaakceptować odpowiedź
Ferran Salguero,
15
Nie, to nie jest prawdziwa odpowiedź. Sprawi to, że twoje zapytanie zostanie wykonane po stronie klienta. Szczegółowe informacje można znaleźć w tym pytaniu: stackoverflow.com/questions/33375998/...
Luke Vo
1
@DatVM zależy od tego, co zamierzasz zrobić. nie zawsze może to być dobry wybór i oczywiście nie zawsze może być zły wybór.
Amir Oveisi
3
Chociaż zgadzam się z tobą, OP stwierdził, że używa EntityFramework. W większości przypadków, podczas pracy z EF, chcesz, aby strona bazy danych wykonała jak najwięcej pracy. Byłoby miło, gdybyś odnotował przypadek w swojej odpowiedzi.
Luke Vo
39

Oznacza to, że nie można używać wyrażeń lambda z „treścią instrukcji” (tzn. Wyrażeń lambda, które używają nawiasów klamrowych) w miejscach, w których wyrażenie lambda należy przekonwertować na drzewo wyrażeń (co ma miejsce na przykład w przypadku korzystania z linq2sql) .

sepp2k
źródło
37
Lekko przeformułowałeś błąd. Odpowiedź Tima Rogersa była znacznie lepsza
vbullinger
2
@vbullinger masz rację do pewnego stopnia, ale w bardziej ogólnym znaczeniu (poza kontekstem linq-to-sql) jest to bardziej bezpośrednia odpowiedź. Pomogło mi to z błędem
AutoMapper
1
vbullinger: Pomogło mi to.
Paweł
7

Nie wiedząc więcej o tym, co robisz (Linq2Objects, Linq2Entities, Linq2Sql?), Powinno to umożliwić:

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

    return new Obj() { 
        Var1 = someLocalVar,
        Var2 = o.var2 
    }; 
}).ToArray();
wydać
źródło
11
Zmusza to kwerendę do oceny.
smartcaveman
Jednak w tych okolicznościach jest to w porządku, ponieważ i tak wywołuje ToArray ().
smartcaveman
2
niekoniecznie - kto wie, jak duże jest „o”? może mieć 50 właściwości, gdy wszystko, czego chcemy, to 2.
kdawg
1
Korzystając z tej techniki, lubię wybierać pola, które wykorzystam do anonimowego typu, zanim zadzwonię.AsEnumerable()
Blake Mitchell,
4

Użyj tego przeciążenia select:

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

    return new Obj() 
    { 
       Var1 = someLocalVar,
       Var2 = o.var2 
    };
})).ToArray();
Mohsen
źródło
Działa to dla mnie, ale czy w połączeniu z Entity Framework to rozwiązanie zapobiegłoby ładowaniu najpierw przez dbcontext wszystkich wierszy do pamięci, tak jak zrobiłby to AsEnumerable ()?
parlament
2
@parlament: Aby zapobiec ładowaniu wszystkich wierszy do pamięci, powinieneś użyć Expression<Func<Obj,Obj>>.
Mohsen
4

Obiekt zwrotny LINQ to SQL implementował IQueryableinterfejs. Zatem dla Selectparametru predykatu metody należy podać tylko jedno wyrażenie lambda bez treści.

Wynika to z faktu, że LINQ dla kodu SQL nie jest wykonywany wewnątrz programu, a nie po stronie zdalnej, takiej jak SQL Server lub inne. Ten leniwy typ ładowania został osiągnięty poprzez wdrożenie IQueryable, gdzie oczekiwany delegat jest zawijany w klasę typu Expression, jak poniżej.

Expression<Func<TParam,TResult>>

Drzewo wyrażeń nie obsługuje wyrażenia lambda w ciele, a jego jedyne wsparcie wyrażenia lambda w jednym wierszu, jak var id = cols.Select( col => col.id );

Więc jeśli spróbujesz, poniższy kod nie zadziała.

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

Poniższe będzie działać zgodnie z oczekiwaniami.

Expression<Func<int,int>> function = x => x * 2;
Azri Jamil
źródło
2

Oznacza to, że ekspresja N typu TDelegate, który zawiera ([parameters]) => { some code };nie może być przekształcony Expression<TDelegate>. To reguła.

Uprość swoje zapytanie. Ten, który podałeś, możesz przepisać w następujący sposób i skompilujesz:

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

Czy Arrtyp podstawowy Obj? Czy istnieje klasa Obj? Twój kod działałby tylko wtedy, gdy Arr jest podstawowym typem Obj. Możesz spróbować zamiast tego:

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

    return new Obj() 
    { 
       Var1 = someLocalVar,
       Var2 = o.var2 
    };
}).ToArray();
Atanas Korczew
źródło
1

W twoim konkretnym przypadku ciało służy do utworzenia zmiennej, a przełączenie na IEnumerablezmusi wszystkie operacje do przetworzenia po stronie klienta, proponuję następujące rozwiązanie.

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

Edycja: Zmień nazwę dla konwencji kodowania C #

Luke Vo
źródło