2017-08-23 2 views
1

J'ai une liste de listes de conditions envoyées depuis le client. J'ai besoin de prendre cette liste et de créer une clause dynamic where à exécuter par EntityFramework.Arbre d'expression pour créer une erreur de lancement de clause Where dynamique liée aux paramètres

Chaque condition a un opérateur, un attribut et une valeur de droite.

Chaque liste de conditions doit être associée ensemble.

Chaque liste de listes de conditions doit être associée entre elles.

Donc, si nous avions

{ 
    "ConditionLists":[ 
     [ 
     { 
      "LhsAttributeDefinition":{ 
       "attribute":{ 
        "key":"isHighBandwidth", 
        "value":"IsHighBandwidth" 
       } 
      }, 
      "Operator":{ 
       "name":"equals", 
       "description":"=", 
       "validation":"", 
       "inputType":"dropdown" 
      }, 
      "RhsValue":"true" 
     }, 
     { 
      "LhsAttributeDefinition":{ 
       "attribute":{ 
        "key":"isForMobile", 
        "value":"IsForMobile" 
       } 
      }, 
      "Operator":{ 
       "name":"equals", 
       "description":"=", 
       "validation":"", 
       "inputType":"dropdown" 
      }, 
      "RhsValue":"true" 
     } 
     ], 
     [ 
     { 
      "LhsAttributeDefinition":{ 
       "attribute":{ 
        "key":"isHighBandwidth", 
        "value":"IsHighBandwidth" 
       } 
      }, 
      "Operator":{ 
       "name":"equals", 
       "description":"=", 
       "validation":"", 
       "inputType":"dropdown" 
      }, 
      "RhsValue":"true" 
     }, 
     { 
      "LhsAttributeDefinition":{ 
       "attribute":{ 
        "key":"isForTablet", 
        "value":"IsForTablet" 
       } 
      }, 
      "Operator":{ 
       "name":"equals", 
       "description":"=", 
       "validation":"", 
       "inputType":"dropdown" 
      }, 
      "RhsValue":"true" 
     } 
     ] 
    ] 
} 

Cela devrait générer .Where(x => (x.isHighBandwidth == true && x.isForMobile == true) || (x.isHighBandwidth == true && x.isForTablet == true))

Voici ce que je dois accomplir cela en utilisant la bibliothèque d'expression:

MethodInfo contains = typeof(string).GetMethod("Contains", new[] { typeof(string) }); 
Expression finalExpression = null; 
List<ParameterExpression> paramsArray = new List<ParameterExpression>(); 
foreach (var conditionList in conditionLists) 
{ 
    Expression andGroup = null; 
    foreach (var condition in conditionList) 
    { 
     Expression expression = null; 
     ParameterExpression param = null; 
     ConstantExpression constant = null; 
     switch (condition.LhsAttributeDefinition.Attribute.Key) 
     { 
      case "title": 
       param = Expression.Parameter(typeof(string), "LearningItem.Title"); 
       constant = Expression.Constant(condition.RhsValue, typeof(string)); 
       expression = Expression.Call(param, contains, constant); 
       break; 
      case "isHighBandwidth": 
       param = Expression.Parameter(typeof(string), "IsHighBandwidth"); 
       constant = Expression.Constant(condition.RhsValue, typeof(string)); 
       expression = Expression.Equal(param, constant); 

       break; 
      case "isForMobile": 
       param = Expression.Parameter(typeof(string), "IsForMobile"); 
       constant = Expression.Constant(condition.RhsValue, typeof(string)); 
       expression = Expression.Equal(param, constant); 

       break; 
      case "isForTablet": 
       param = Expression.Parameter(typeof(string), "IsForTablet"); 
       constant = Expression.Constant(condition.RhsValue, typeof(string)); 
       expression = Expression.Equal(param, constant); 

       break; 

     } 
     paramsArray.Add(param); 
     if (andGroup != null) 
     { 
      Expression.And(andGroup, expression); 
     } 
     else 
     { 
      andGroup = expression; 
     } 
    } 
    //OR the expression tree created above 

    if (finalExpression != null) 
    { 
     Expression.Or(finalExpression, andGroup); 
    } 
    else 
    { 
     finalExpression = andGroup; 
    } 
} 
MethodCallExpression whereCallExpression = Expression.Call(
    typeof(Queryable), 
    "Where", 
    new Type[] { query.ElementType }, 
    query.Expression, 
    Expression.Lambda<Func<Activity, bool>>(finalExpression, paramsArray.ToArray<ParameterExpression>())); 
return query; 

Donc, ma pensée est que l'intérieur d'une boucle imbriquée, je construis les requêtes AND et les requêtes OU comme une grande expression, puis je crée la requête lambda à la toute fin. Je collecte les paramètres le long du chemin dans un paramsArray (liste).

Mon problème est que lors de l'exécution, il explose en disant que 'ParameterExpression of type 'System.String' cannot be used for delegate parameter of type 'INOLMS.Data.Activity''. Je suppose que c'est parce que le paramètre que j'ai collecté jusqu'ici est juste une chaîne (mon exemple de corps de requête est juste une seule condition avec IsHighBandwidth true), et il n'aime pas que je prenne un paramètre string et essaye pour obtenir une requête Activity.

Qu'est-ce que je fais mal ici?

Répondre

2

Il y a beaucoup de problèmes avec le code que vous avez actuellement. Commençons par cas par cas.

Supposons que vous voulez transformer

{ 
    "LhsAttributeDefinition":{ 
     "attribute":{ 
      "key":"isHighBandwidth", 
      "value":"IsHighBandwidth" 
     } 
    }, 
    "Operator":{ 
     "name":"equals", 
     "description":"=", 
     "validation":"", 
     "inputType":"dropdown" 
    }, 
    "RhsValue":"true" 
} 

en .Where(x => x.IsHighBandwidth == true).

Alors d'abord de tout ce que vous avez à construire le côté gauche de l'expression qui est x.IsHighBandwidth et vous ne pouvez pas définir simplement le paramètre de type chaîne avec une valeur constante IsHighBandwidth (qui est ce que vous avez fait dans Expression.Parameter(typeof(string), "IsHighBandwidth"). Pour ce faire, vous devez d'abord paramètre entrez Activity puis vous appelez Expression.MakeMemberAccess avec appropriée MemberInfo objet représentant la propriété désirée Quelque chose comme ceci:.

var p = Expression.Parameter(typeof(Activity)); 
var accessorExp = Expression.MakeMemberAccess(p, typeof(Activity).GetProperty("IsHighBandwidth")); 

maintenant que nous avons le côté gauche pris en charge, nous allons jeter un coup d'œil sur le côté droit Si votre propriété est. tapez bool et vous voulez faire l'égalité vérifier puis à droite doit matc h aussi. Vous ne pouvez pas simplement créer une constante de chaîne et attendre une sorte de magie pour analyser ce type en bool.Dans notre cas, nous savons que nous nous attendons à valeur bool donc nous devons analyser la chaîne bool d'abord, puis créer l'expression constante de type bool:

bool value = Boolean.Parse(condition.RhsValue); // add error checks 
var valueExpr = Expression.Constant(value); 

Maintenant que nous avons le côté gauche et à droite pris en charge et du type correct, vous pouvez construire l'expression de l'égalité que vous avez fait dans votre code:

var expression = Expression.Equal(accessorExpr, valueExpr); 

maintenant que nous avons construit le corps (avec bool le type d'expression), il faut construire lambda qui sera transmis comme argument lambda. Comme vous le voyez par le code C#, ce lambda accepte exactement un paramètre de type Activity et renvoie bool. Vous ne pouvez pas envoyer plusieurs paramètres comme vous l'avez fait dans votre code. Exemple:

// Parameter p must be the same as was defined above 
var lambda = Expression.Lambda(expression, new [] { p }); 

Et maintenant que nous avons le corps que vous pouvez construire une nouvelle expression d'appel de méthode pour Where que vous avez fait dans votre code, avec une différence importante: vous devez citer l'expression lambda si vous voulez des paramètres externes au travail (ce est ce que la méthode LINQ Where ne behind the scene):

var whereCallExpression = Expression.Call(
    typeof(Queryable), 
    "Where", 
    new Type[] { query.ElementType }, 
    query.Expression, 
    Expression.Quote(lambda)); 

Cela devrait être suffisamment détaillé pour vous aider à démarrer. Vous devez garder à l'esprit que les expressions LINQ sont vraiment de bas niveau et vous devez veiller à produire vous-même un arbre d'expression valide. Il n'y a pas de magie de compilateur à laquelle vous pourriez être habitué lors de la programmation en C# (par exemple des conversions implicites).

+0

Wow, c'était super utile. Merci beaucoup! –