2009-10-20 4 views
5

J'ai une méthode qui prend actuellement un Func<Product, string> comme paramètre, mais j'en ai besoin pour être un Expression<Func<Product, string>>. En utilisant AdventureWorks, voici un exemple de ce que je voudrais faire en utilisant le Func.Refactoring Func <T> en Expression <Func<T>>

private static void DoSomethingWithFunc(Func<Product, string> myFunc) 
{ 
    using (AdventureWorksDataContext db = new AdventureWorksDataContext()) 
    { 
     var result = db.Products.GroupBy(product => new 
     { 
      SubCategoryName = myFunc(product), 
      ProductNumber = product.ProductNumber 
     }); 
    } 
} 

Je voudrais qu'il ressemble à quelque chose comme ceci:

private static void DoSomethingWithExpression(Expression<Func<Product, string>> myExpression) 
{ 
    using (AdventureWorksDataContext db = new AdventureWorksDataContext()) 
    { 
     var result = db.Products.GroupBy(product => new 
      { 
       SubCategoryName = myExpression(product), 
       ProductNumber = product.ProductNumber 
      }); 
    } 
} 

Cependant, le problème que je suis en cours d'exécution en est que myExpression(product) est invalide (ne compilera pas). Après avoir lu quelques autres messages, je comprends pourquoi. Et si ce n'était pas le fait que j'ai besoin pour la variable product la deuxième partie de ma clé, je pourrais probablement dire quelque chose comme ceci:

var result = db.Products.GroupBy(myExpression); 

Mais je besoin la variable product parce que je besoin du deuxième partie de la clé (ProductNumber). Donc je ne suis pas vraiment sûr de ce qu'il faut faire maintenant. Je ne peux pas le laisser comme un Func parce que cela cause des problèmes. Je ne peux pas comprendre comment utiliser une expression parce que je ne vois pas comment je pourrais passer la variable product. Des idées?

EDIT: Voici un exemple de la façon dont je qualifierais la méthode:

DoSomethingWithFunc(product => product.ProductSubcategory.Name); 

Répondre

4

Il n'y a pas moyen de raccorder un arbre d'expression qui est représentée comme un objet Expression<T> dans un milieu d'un « arbre littéral » représenté par lambda expression. Vous devrez construire un arbre d'expression pour passer à GroupBy manuellement:

// Need an explicitly named type to reference in typeof() 
private class ResultType 
{ 
    public string SubcategoryName { get; set; } 
    public int ProductNumber { get; set; }| 
} 

private static void DoSomethingWithExpression(
    Expression<Func<Product, 
    string>> myExpression) 
{ 
    var productParam = Expression.Parameter(typeof(Product), "product"); 
    var groupExpr = (Expression<Func<Product, ResultType>>)Expression.Lambda(
     Expression.MemberInit(
      Expression.New(typeof(ResultType)), 
      Expression.Bind(
       typeof(ResultType).GetProperty("SubcategoryName"), 
       Expression.Invoke(myExpression, productParam)), 
      Expression.Bind(
       typeof(ResultType).GetProperty("ProductNumber"), 
       Expression.Property(productParam, "ProductNumber"))), 
     productParam); 
    using (AdventureWorksDataContext db = new AdventureWorksDataContext()) 
    { 
     var result = db.Products.GroupBy(groupExpr); 
    } 
} 
+0

Nice! La dernière ligne ne compile pas pour moi, cependant, où le résultat est assigné. "Les arguments de type pour la méthode ... ne peuvent pas être déduits de l'utilisation." Est-ce que je manque quelque chose? – Ecyrb

+1

La valeur renvoyée par 'Expression.Lambda' doit être convertie en' Expression > '. –

+0

Excellent! Cela s'est avéré plus complexe que ce à quoi je m'attendais. Je n'ai jamais fait ma propre Expression comme ça auparavant, donc j'étudierai ce code pour être sûr de bien comprendre ce qui se passe. Merci! – Ecyrb

3

À la réflexion, la compilation de l'expression ne fonctionnera pas.

Vous devrez créer votre expression GroupBy à la main, ce qui signifie que vous ne pouvez pas utiliser le type anonyme. Je suggère de construire le reste de votre expression, puis décompiler pour voir l'arbre d'expression généré. Le résultat final ressemblera à quelque chose comme ça, en utilisant des pièces de myExpression selon le cas:

private static void DoSomethingWithExpression(Expression<Func<Product, string>> myExpression) 
{ 
    var productParam = myExpression.Parameters[0]; 

    ConstructorInfo constructor = ...; // Get c'tor for return type 

    var keySelector = Expression.Lambda(
          Expression.New(constructor, 
           new Expression[] { 
            productParam.Body, 
            ... // Expressions to init other members 
           }, 
           new MethodInfo[] { ... }), // Setters for your members 
          new [] { productParam }); 

    using (AdventureWorksDataContext db = new AdventureWorksDataContext()) 
    { 
     var result = db.Products.GroupBy(keySelector); 

     // ... 
    } 
} 
+0

Vous avez manqué la raison pour laquelle il se déplace de '' Func' à Expression' en premier lieu - il veut que cela fonctionne dans LINQ to SQL contexte, et cela ne permettra pas les appels aléatoires de fonction (ou de délégué) dans une requête. –

+0

Oui, je vois ça maintenant - travailler sur un Edit. – dahlbyk

+0

Vous avez battu Pavel au coup de poing mais sa réponse était plus claire pour moi. +1 pour votre aide, cependant. Merci! – Ecyrb