"Une expression lambda avec un corps d'instruction ne peut pas être convertie en une arborescence d'expression"

185

En utilisant EntityFramework , j'obtiens l'erreur " A lambda expression with a statement body cannot be converted to an expression tree" en essayant de compiler le code suivant:

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

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

Je ne sais pas ce que signifie l'erreur et surtout comment y remédier. De l'aide?

pistache
la source
6
essayez de convertir en liste comme celle-ci. objets.Liste (). Sélectionnez (...
nelson eldoro

Réponses:

118

Un objectscontexte de base de données Linq-To-SQL? Dans ce cas, vous ne pouvez utiliser que des expressions simples à droite de l'opérateur =>. La raison en est que ces expressions ne sont pas exécutées, mais sont converties en SQL pour être exécutées sur la base de données. Essaye ça

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

Vous pouvez utiliser le corps de l'instruction dans l'expression lamba pour les collections IEnumerable . essaye celui-là:

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

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

Remarque:
réfléchissez bien lorsque vous utilisez cette méthode, car de cette façon, vous aurez tous les résultats de la requête en mémoire, ce qui peut avoir des effets secondaires indésirables sur le reste de votre code.

Amir Oveisi
la source
4
+1 J'aime ça! L'ajout de AsEnumerable()masques mon problème disparaît!
Joel
5
C'est la vraie solution, la réponse acceptée est difficile à appliquer dans certains cas
Ferran Salguero
18
Non, ce n'est pas la vraie réponse. Cela rendrait votre requête exécutée côté client. Reportez-vous à cette question pour plus de détails: stackoverflow.com/questions/33375998/…
Luke Vo
1
@DatVM cela dépend de ce que vous allez faire. cela ne peut pas être toujours le bon choix et bien sûr ne peut pas toujours être un mauvais choix.
Amir Oveisi
4
Bien que je sois d'accord avec vous, le PO a déclaré qu'il utilisait EntityFramework. Dans la plupart des cas, lorsque vous travaillez avec EF, vous voulez que le côté base de données fasse autant de travail que possible. Ce serait bien si vous notiez le cas dans votre réponse.
Luke Vo
41

Cela signifie que vous ne pouvez pas utiliser d'expressions lambda avec un "corps de déclaration" (c'est-à-dire des expressions lambda qui utilisent des accolades) aux endroits où l'expression lambda doit être convertie en un arbre d'expression (ce qui est par exemple le cas lors de l'utilisation de linq2sql) .

sepp2k
la source
43
Vous ... avez légèrement reformulé l'erreur. La réponse de @Tim Rogers était bien meilleure
vbullinger
3
@vbullinger vous avez raison dans une certaine mesure, mais dans un sens plus général (en dehors du contexte de linq-to-sql), c'est une réponse plus directe. Cela m'a aidé avec une erreur d'
AutoMapper
1
vbullinger: Cela m'a aidé, cependant.
Paul le
8

Sans en savoir plus sur ce que vous faites (Linq2Objects, Linq2Entities, Linq2Sql?), Cela devrait le faire fonctionner:

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

    return new Obj() { 
        Var1 = someLocalVar,
        Var2 = o.var2 
    }; 
}).ToArray();
dépensier
la source
12
Cela force le questionnable à évaluer.
smartcaveman
Cependant, dans ce cas, ce n'est pas grave, car il appelle ToArray () juste après de toute façon.
smartcaveman
2
pas nécessairement - qui sait quelle est la taille de «o»? il pourrait avoir 50 propriétés alors que tout ce que nous voulons est 2.
kdawg
1
Lorsque j'utilise cette technique, j'aime sélectionner les champs que j'utiliserai dans un type anonyme avant d'appeler.AsEnumerable()
Blake Mitchell
5

L'objet de retour LINQ to SQL implémentait l' IQueryableinterface. Donc, pour le Selectparamètre de prédicat de méthode, vous ne devez fournir qu'une seule expression lambda sans corps.

En effet, le code LINQ pour SQL n'est pas exécuté à l'intérieur du programme plutôt que du côté distant comme le serveur SQL ou d'autres. Ce type d'exécution de chargement paresseux a été obtenu en implémentant IQueryable où son délégué expect est encapsulé dans la classe de type Expression comme ci-dessous.

Expression<Func<TParam,TResult>>

L'arbre d'expression ne prend pas en charge l'expression lambda avec body et sa seule prend en charge l'expression lambda sur une seule ligne comme var id = cols.Select( col => col.id );

Donc, si vous essayez le code suivant ne fonctionnera pas.

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

Ce qui suit fonctionnera comme prévu.

Expression<Func<int,int>> function = x => x * 2;
Azri Jamil
la source
4

Utilisez cette surcharge de select:

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

    return new Obj() 
    { 
       Var1 = someLocalVar,
       Var2 = o.var2 
    };
})).ToArray();
Mohsen
la source
Cela fonctionne pour moi, mais lorsqu'elle est utilisée avec Entity Framework, cette solution empêcherait-elle le dbcontext de charger toutes les lignes en mémoire en premier, comme le ferait AsEnumerable ()?
parlement
2
@parliament: Pour éviter de charger toutes les lignes en mémoire, vous devez utiliser Expression<Func<Obj,Obj>>.
Mohsen
2

Cela signifie qu'une expression Lambda de type TDelegatequi contient un ([parameters]) => { some code };ne peut pas être convertie en un Expression<TDelegate>. C'est la règle.

Simplifiez votre requête. Celui que vous avez fourni peut être réécrit comme suit et compilera:

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

9 ans trop tard pour la fête, mais une approche différente de votre problème (que personne n'a évoqué?):

Le corps de l'instruction fonctionne bien avec Func<>mais ne fonctionnera pas avec Expression<Func<>>. IQueryable.Selectveut un Expression<>, car ils peuvent être traduits pour Entity Framework - Func<>ne peut pas.

Donc, soit vous utilisez le AsEnumerableet commencez à travailler avec les données en mémoire (non recommandé, sinon vraiment nécessaire), soit vous continuez à travailler avec ce IQueryable<>qui est recommandé. Il y a quelque chose linq queryqui s'appelle qui facilite certaines choses:

IQueryable<Obj> result = from o in objects
                         let someLocalVar = o.someVar
                         select new Obj
                         {
                           Var1 = someLocalVar,
                           Var2 = o.var2
                         };

avec letvous pouvez définir une variable et l'utiliser dans le select(ou where, ...) - et vous continuez à travailler avec le IQueryablejusqu'à ce que vous ayez vraiment besoin d'exécuter et d'obtenir les objets.

Ensuite, vous pouvez Obj[] myArray = result.ToArray()

Matthias Burger
la source
1
J'ai juste regardé ça! J'aurais aimé voir l'intrigue sur mon visage quand j'ai vu un article de 9 ans avoir un "Une nouvelle réponse a été ajoutée à ce message". Hahaha Bon timing.
TeaBaerd
@TeaBaerd haha ​​ouais. : D coïncidences sont parfois drôles ...
Matthias Burger
1

Un Arrtype de base est-il Obj? La classe Obj existe-t-elle? Votre code ne fonctionnerait que si Arr est un type de base d'Obj. Vous pouvez essayer ceci à la place:

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

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

Pour votre cas particulier, le corps est pour créer une variable, et le passage à IEnumerableforcera toutes les opérations à être traitées côté client, je propose la solution suivante.

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

Edit: Renommer pour la convention de codage C #

Luke Vo
la source