2010-02-09 7 views
12

J'ai suivi ce fil: link textAppend à une expression

Jason donne un exemple:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right) 
{ 
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters); 
} 

et son utilisation en tant que tel:

Expression<Func<Client, bool>> clientWhere = c => true; 
if (filterByClientFName) 
{ 
    clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName); 
} 
if (filterByClientLName) 
{ 
    clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName); 
} 

J'ai une table de commandes et je suivais l'exemple ci-dessus, en changeant les noms des colonnes, et j'obtiens l'erreur similaire que le post-créateur avait

L'opérateur binaire AndAlso n'est pas défini pour les types 'System.Func 2[Models.Order,System.Boolean]' and 'System.Func 2 [Models.Order, System.Boolean]'.

Quelqu'un a-t-il une idée de ce qui me manque?

MISE À JOUR:

Eric, je encore suivi ce que l'utilisateur du poste précédent demandait, ici link text

L'utilisateur a ce

Expression<Func<Client, bool>> clientWhere = c => true; 
Expression<Func<Order, bool>> orderWhere = o => true; 
Expression<Func<Product, bool>> productWhere = p => true; 

if (filterByClient) 
{ 
    clientWhere = c => c.ClientID == searchForClientID; 
} 

Maintenant, s'il devait avoir diverses conditions dans filterByClient, dites qu'il a soit clientid et/ou un autre nom de colonne, comment pourrait-on construire l'expression clientWhere?

Répondre

29

Vous essayez de construire un arbre d'expression qui représente ceci:

c => true && c.ClientFName == searchForClientFName 

Vous êtes en train de construire un arbre d'expression qui représente ceci:

c => c=> true && c => c.ClientFName == searchForClientFName 

qui n'a pas de sens du tout.

Maintenant, vous pourriez penser naïvement que cela fonctionne:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right) 
{ 
// NOTICE: Combining BODIES: 
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left.Body, right.Body), left.Parameters); 
} 

qui produirait dans votre cas quelque chose qui représente

c => true && c.ClientFName == searchForClientFName 

Ce qui ressemble à droite. Mais en fait, c'est fragile. Supposons que vous ayez

... d => d.City == "London" ... 
... c => c.ClientName == "Fred Smith" ... 

et que vous ayez utilisé cette méthode pour les combiner. Vous obtiendriez un objet représentant

c => d.City == "London" && c.ClientName == "Fred Smith" 

Que diable d fait là-bas?

En outre, les paramètres sont appariés par l'identité de l'objet, non par nom de paramètre.Si vous faites cela

... c => c.City == "London" ... 
... c => c.ClientName == "Fred Smith" ... 

et de les combiner dans

c => c.City == "London" && c.ClientName == "Fred Smith" 

vous êtes dans le même bateau; le "c" dans "c.City" est un différent c que les deux autres.

Ce que vous devez vraiment faire est de faire un troisième paramètre objet, substitut dans les corps des deux lambdas pour chaque occurence de leurs paramètres, puis construire un arbre d'expression nouvelle lambda du résultat corps substitués.

Vous pouvez créer un moteur de substitution en écrivant un visiteur qui passe sur le corps de l'arbre d'expression, en le réécrivant au fur et à mesure.

+4

Cela ressemble à une réponse intelligente à la question de savoir pourquoi le problème existe, mais vous n'avez pas vraiment donné de solution pour que vous puissiez bénéficier de votre poste ... –

+7

@Michael: Alors je vous invite à écrire une réponse tu préfères. –

+0

J'ai fait quelque chose de similaire à ce qu'Eric suggère ici: http://stackoverflow.com/questions/14248674/system-linq-expressions-binding-lambdaexpression-inputs-at-runtime Vous pouvez le trouver utile. –

11

Il m'est difficile de comprendre le answer de hvd, j'ai donc créé du code pour l'expliquer différemment. hvd devrait avoir le mérite de suggérer l'ExpressionVisitor. Je ne pouvais pas comprendre l'exemple dans le contexte des fonctions d'entrée de type Linq à X que j'utilisais.

J'espère que cela aidera quelqu'un d'autre à venir à la question de ce point de vue.

J'ai également créé le code de combinaison en tant que méthodes d'extension pour le rendre un peu plus facile à utiliser.


using System; 
using System.Collections.Generic; 
using System.Linq.Expressions; 

namespace ConsoleApplication3 
{ 

    class Program 
    { 

     static void Main(string[] args) 
     { 

      var combined = TryCombiningExpressions(c => c.FirstName == "Dog", c => c.LastName == "Boy"); 

      Console.WriteLine("Dog Boy should be true: {0}", combined(new FullName { FirstName = "Dog", LastName = "Boy" })); 
      Console.WriteLine("Cat Boy should be false: {0}", combined(new FullName { FirstName = "Cat", LastName = "Boy" })); 

      Console.ReadLine(); 
     } 

     public class FullName 
     { 
      public string FirstName { get; set; } 
      public string LastName { get; set; } 
     } 

     public static Func<FullName, bool> TryCombiningExpressions(Expression<Func<FullName, bool>> func1, Expression<Func<FullName, bool>> func2) 
     { 
      return func1.CombineWithAndAlso(func2).Compile(); 
     } 
    } 

    public static class CombineExpressions 
    { 
     public static Expression<Func<TInput, bool>> CombineWithAndAlso<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2) 
     { 
      return Expression.Lambda<Func<TInput, bool>>(
       Expression.AndAlso(
        func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)), 
       func1.Parameters); 
     } 

     public static Expression<Func<TInput, bool>> CombineWithOrElse<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2) 
     { 
      return Expression.Lambda<Func<TInput, bool>>(
       Expression.AndAlso(
        func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)), 
       func1.Parameters); 
     } 

     private class ExpressionParameterReplacer : ExpressionVisitor 
     { 
      public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters) 
      { 
       ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>(); 
       for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++) 
        ParameterReplacements.Add(fromParameters[i], toParameters[i]); 
      } 

      private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements { get; set; } 

      protected override Expression VisitParameter(ParameterExpression node) 
      { 
       ParameterExpression replacement; 
       if (ParameterReplacements.TryGetValue(node, out replacement)) 
        node = replacement; 
       return base.VisitParameter(node); 
      } 
     } 
    } 
} 
+0

Merci l'homme! Cela devrait être la réponse acceptée! – apostolov

+0

Cela a fonctionné. Merci beaucoup. :-) –

0

Si vous en avez besoin, je créé une petite bibliothèque couramment pour créer des fonctions lambda à la volée sans faire face directement avec System.Linq.Expressions. Et il peut facilement gérer le genre de situation. Juste pour donner un exemple:

static void Main(string[] args) 
{ 
    var firstNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.FirstName); 
    var lastNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.LastName); 

    Func<FullName, bool> combined = (a) => firstNameCompare(a, "Dog") && lastNameCompare(a, "Boy"); 

    var toCheck = new FullName {FirstName = "Dog", LastName = "Boy"}; 
    Console.WriteLine("Dog Boy should be true: {0}", combined(toCheck)); 
    toCheck = new FullName {FirstName = "Cat", LastName = "Boy"}; 
    Console.WriteLine("Cat Boy should be false: {0}", combined(toCheck)); 

    Console.ReadLine(); 
} 

Les méthodes GetComparer cherchent la propriété a été transférée comme expression et trouver ho pour obtenir sa valeur, il construit une nouvelle expression qui se chargera de la comparaison. A la fin, les deux fonctions sont évaluées en appelant la fonction "combinée".

Si vous avez besoin de plus de vérifications, vous pouvez utiliser un tableau et itérer sur l'intérieur de la « lambda combiné »

Le code et la documentation de la bibliothèque sont ici: Kendar Expression Builder Alors que le paquet NuGet est ici: Nuget Expression Builder

0

J'ai essayé d'implémenter ce genre de choses. Il m'a fallu un jour pour le savoir. Ma solution est basée sur un filtre dans une boucle basée sur un tableau de prédicat. À titre de remarque, il s'agit d'une réflexion générique et basée entièrement car les seules informations sur la classe et le champ sont les chaînes. Pour simplifier, j'appelle directement la classe Model mais dans un projet, vous devez passer par un contrôleur qui appelle le modèle.

Alors on y va: La partie du modèle où T est un générique dans la classe

public class DALXmlRepository<T> where T : class 
    { 
    public T GetItem(Array predicate) 
    { 
     IQueryable<T> QueryList = null; 

     QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0)); 
     for (int i = 1; i < predicate.GetLength(0); i++) 
     { 
      QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i)); 
     } 

     if (QueryList.FirstOrDefault() == null) 
      throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found."); 
     return QueryList.FirstOrDefault(); 
    } 
    } 

Maintenant, le constructeur LambdaExpression, il est une de base (avec type String ou autre chose), vous pouvez l'améliorer avec plus functionnality:

private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue) 
    { 
     LambdaExpression lambda = null; 

     Expression Criteria = null; 

     Random r = new Random(); 
     ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString()); 

     if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string)) 
     { 
      Expression left = Expression.PropertyOrField(predParam, FieldName); 
      Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null); 
      //Type du champ recherché 
      Type propType = GenericArgument.GetProperty(FieldName).PropertyType; 
      Expression right = Expression.Constant(FieldValue, propType); 
      Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null); 
      Criteria = Expression.Equal(LefttoUpper, RighttoUpper); 
     } 
     else 
     { 
      Expression left = Expression.PropertyOrField(predParam, FieldName); 
      Type propType = GenericArgument.GetProperty(FieldName).PropertyType; 
      Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType); 

      Criteria = Expression.Equal(left, right); 
     } 

     lambda = Expression.Lambda(Criteria, predParam); 
     return lambda; 
    } 

maintenant, la fonction d'appel:

public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter) 
    { 
     //Get the type 
     Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel"); 
     Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType(type); 
     //Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML); 
     ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) }); 
     IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null }); 

     //Building the string type Expression<func<T,bool>> to init the array 
     Type FuncType = typeof(Func<,>).MakeGenericType(type ,typeof(bool)); 
     Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType); 
     Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count); 

     MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() }); 

     if (method == null) 
      throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name); 

     int j = 0; 
     IDictionaryEnumerator criterias = FieldFilter.GetEnumerator(); 
     criterias.Reset(); 
     while (criterias.MoveNext()) 
     { 
      if (!String.IsNullOrEmpty(criterias.Key.ToString())) 
      { 
       lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j); 
      } 
      else 
      { 
       throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString()); 
      } 
      j++; 
     } 

     Object item = method.Invoke(DalInstance, new object[] { lambda }); 
     } 

Les arguments sont: Entité de chaîne: Nom de classe d'entité. XMLContext: c'est l'unité de travail du référentiel, argument que j'utilise pour initialiser la classe Model Hashtable FieldsNameToGet: Index/valeur de la liste du champ que je veux récupérer Hashtable FieldFilter: la clé/Value avec FieldName/Contenu utilisé pour produire l'expression Lambda

Bonne chance.