2017-08-23 2 views
1

Ich habe eine Liste von Listen der Bedingungen, die vom Client gesendet werden. Ich muss diese Liste nehmen und eine dynamische where-Klausel erstellen, die von EntityFramework ausgeführt wird.Ausdrucksbaum zum Erstellen von dynamischen Where-Klausel werfen Fehler im Zusammenhang mit Parametern

Jede Bedingung hat einen Operator, ein Attribut und einen Wert auf der rechten Seite.

Jede Liste von Bedingungen muss ANDed zusammen sein.

Jede Liste von Bedingungen muss OR-verknüpft werden.

Also, wenn wir hatten

{ 
    "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" 
     } 
     ] 
    ] 
} 

Das .Where(x => (x.isHighBandwidth == true && x.isForMobile == true) || (x.isHighBandwidth == true && x.isForTablet == true))

Hier erzeugen sollte ist, was ich habe dies die Expression Bibliothek erreichen mit:

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; 

Also mein Gedanke ist, dass im Inneren einer verschachtelten for-Schleife, ich baue die AND-Abfragen und die OR-Abfragen als einen großen Ausdruck, und dann erstelle ich die Lambda-Abfrage ganz am Ende. Ich sammle Parameter auf dem Weg in eine paramsArray (Liste).

Mein Problem ist, dass bei der Ausführung, explodiert es sagen, dass 'ParameterExpression of type 'System.String' cannot be used for delegate parameter of type 'INOLMS.Data.Activity''. Ich nehme an, dies ist, weil der Parameter, den ich bisher gesammelt habe, nur eine Zeichenfolge ist (mein Beispiel Anfrage Körper ist nur eine einzelne Bedingung mit IsHighBandwidth true), und es mag nicht, dass ich einen String-Parameter nehme und versuche um eine Activity Abfrage zu erhalten.

Was mache ich hier falsch?

Antwort

2

Es gibt eine Menge Probleme mit dem Code, den Sie derzeit haben. Beginnen wir mit Fall für Fall.

Angenommen, Sie

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

in .Where(x => x.IsHighBandwidth == true) transformieren möchten.

Also zunächst einmal haben Sie linke Seite des Ausdrucks gebaut, die x.IsHighBandwidth ist und Sie können einfach nicht Parameter vom Typ String definieren mit konstantem Wert IsHighBandwidth (das ist, was Sie in Expression.Parameter(typeof(string), "IsHighBandwidth") getan haben. Um dies zu tun, müssen Sie zunächst Parameter geben Sie Activity und dann rufen Sie Expression.MakeMemberAccess mit entsprechenden MemberInfo Objekt darstellt gewünschte Eigenschaft Etwas wie folgt aus:.

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

Jetzt, wo wir Seite gesorgt verlassen haben, lassen Sie uns auf der rechten Seite einen Blick Wenn Ihr Eigentum ist. Geben Sie bool ein und Sie möchten die Gleichheitsprüfung durchführen, dann muss die rechte Seite matc sein h auch. Sie können nicht einfach eine String-Konstante erstellen und irgendeine Art von Magie erwarten, um dies auf den Typ bool zu analysieren.In unserem Fall wissen wir, dass wir Bool Wert erwarten, so dass wir String zuerst in Bool analysieren müssen und dann konstant Ausdruck des Typs erstellen bool:

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

Jetzt, wo wir kümmern rechten Seite verlassen haben und genommen und der richtigen Art, Sie können Gleichheit Ausdruck konstruieren, wie Sie in Sie Code haben:

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

Jetzt, wo wir Körper aufgebaut haben (mit bool Ausdruck Typ), müssen wir Lambda konstruieren, die auf Lambda als Argument übergeben wird. Wie Sie anhand des C# -Codes sehen, akzeptiert dieses Lambda genau einen Parameter vom Typ Activity und gibt bool zurück. Sie können nicht mehrere Parameter wie in Ihrem Code senden. Beispiel:

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

Und jetzt, wo wir Körper haben Sie neuen Methodenaufruf Ausdruck für Where konstruieren können, wie Sie in Ihrem Code haben, mit einem wichtigen Unterschied: Sie müssen Lambda-Ausdruck zitieren, wenn Sie externe Parameter (diese arbeiten möchten ist das, was LINQ Where Methode macht behind the scene):

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

Diese detailliert genug sein, sollten Sie den Start. Sie müssen bedenken, dass LINQ-Ausdrücke wirklich niedrige Werte sind und Sie müssen darauf achten, dass Sie selbst einen gültigen Ausdrucksbaum erzeugen. Es gibt keine Compiler-Magie, mit der Sie bei der Programmierung in C# vertraut sein könnten (z. B. implizite Konvertierungen).

+0

Wow, das war super hilfreich. Vielen Dank! –

Verwandte Themen