2016-09-08 2 views
0

ich eine grundlegende Regel-Engine, die ich in sehr ähnlicher Weise auf die Route gebaut haben hier vorgeschlagen:Bewertung komplexer Ausdrucksbaum

How to implement a rule engine?

Ive es auf der Grundlage weiterer Anforderungen erweitert, und jetzt brauche ich komplexe Klassen zB

EvaluateRule("Transaction.IsOpen", "Equals", "true") 

Der Code in seiner einfachsten Form zu bewerten ist:

var param = inputMessageType; 
left = Expression.Property(param, memberName); 
tProp = typeof(T).GetProperty(r.MemberName).PropertyType; 
right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp)); 
return Expression.MakeBinary(tBinary, left, right);  

Um komplexe Klassen zu bewerten habe ich ein ähnliches Verfahren wie hier:

https://stackoverflow.com/questions/16544672/dynamically-evaluating-a-property-string-with-expressions

Das Problem, dass Im ist, dass wenn ich versuche, die Regel mit einer Eigenschaft einer Klasse (Transaction.IsOpen) zu bewerten , Ich bekomme es mit dem Typ des Stammtyps auf der rechten Seite des Ausdrucks, aber den Typ des komplexen Objekts auf der linken Seite des Ausdrucks.

Dies führt zu dem Fehler:

System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.Func`2[Transaction,System.Boolean]' and 'System.Boolean'. 

Wie überwinden ich dieses Problem? Ich bin kein Experte mit Expression Trees, und viele der Konzepte sind schwer zu verstehen, wenn das Beispiel von der Standarddokumentation abweicht.

Edit: Hier ist der Code (Ive weggelassen einige Sachen, die Umgebung spezifisch ist, um Fokus mit dem Problem halten)

public Actions EvaluateRulesFromMessage(ClientEventQueueMessage message) 
    {    
     var ruleGroups = _ruleRepository.GetRuleList(); 

    var actions = new Actions(); 

    foreach (var ruleGroup in ruleGroups) 
    { 
     if (message.MessageType == "UI_UPDATE") 
     { 
      // clean up json object 
      JObject dsPayload = (JObject.Parse(message.Payload)); 

      var msgParams = JsonConvert.DeserializeObject<UiTransactionUpdate>(message.Payload);      
      msgParams.RulesCompleted = msgParams.RulesCompleted ?? new List<int>(); 

      var conditionsMet = false; 
      // process the rules filtering out the rules that have already been evaluated      
      var filteredRules = ruleGroup.Rules.Where(item => 
       !msgParams.RulesCompleted.Any(r => r.Equals(item.Id)));      

      foreach (var rule in filteredRules)             
      {       
       Func<UiTransactionUpdate, bool> compiledRule = CompileRule<UiTransactionUpdate>(rule, msgParams); 
       if (compiledRule(msgParams)) 
       { 
        conditionsMet = true; 

       } 
       else 
       { 
        conditionsMet = false; 
        break; 
       }       

      } 
      if (conditionsMet) 
      {       
       actions = AddAction(message, ruleGroup); 
       break; 
      } 
     } 
    }     
    return actions; 
} 

public Func<UiTransactionUpdate, bool> CompileRule<T>(Rule r, UiTransactionUpdate msg) 
{ 
    var expression = Expression.Parameter(typeof(UiTransactionUpdate)); 
    Expression expr = BuildExpr<UiTransactionUpdate>(r, expression, msg); 
    // build a lambda function UiTransactionUpdate->bool and compile it 
    return Expression.Lambda<Func<UiTransactionUpdate, bool>>(expr, expression).Compile(); 
} 

static Expression Eval(object root, string propertyString, out Type tProp) 
{ 
    Type type = null; 
    var propertyNames = propertyString.Split('.'); 
    ParameterExpression param = Expression.Parameter(root.GetType()); 
    Expression property = param; 
    string propName = ""; 
    foreach (var prop in propertyNames) 
    {       
     property = MemberExpression.PropertyOrField(property, prop); 
     type = property.Type; 
     propName = prop; 
    } 

    tProp = Type.GetType(type.UnderlyingSystemType.AssemblyQualifiedName); 

    var param2 = MemberExpression.Parameter(tProp); 

    var e = Expression.Lambda(property, param); 

    return e; 
} 

static Expression BuildExpr<T>(Rule r, ParameterExpression param, UiTransactionUpdate msg) 
{ 
    Expression left; 
    Type tProp; 
    string memberName = r.MemberName; 
    if (memberName.Contains(".")) 
    { 
     left = Eval(msg, memberName, out tProp);    
    } 
    else 
    { 
     left = Expression.Property(param, memberName); 
     tProp = typeof(T).GetProperty(r.MemberName).PropertyType; 
    } 

    ExpressionType tBinary;    
    if (ExpressionType.TryParse(r.Operator, out tBinary)) 
    { 
     Expression right=null; 
     switch (r.ValueType) ///todo: this needs to be refactored to be type independent 
     { 
      case TargetValueType.Value: 
       right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp)); 
       break;      
     } 
     // use a binary operation ie true/false 
     return Expression.MakeBinary(tBinary, left, right);     
    } 
    else 
    { 
     var method = tProp.GetMethod(r.Operator); 
     var tParam = method.GetParameters()[0].ParameterType; 
     var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tParam)); 
     // use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)' 
     return Expression.Call(left, method, right); 
    } 
} 
+0

Wie funktioniert Ihre aktuellen Code-Look jetzt? –

+0

Die linke Zuweisung in meinem Basiscode verwendet im Wesentlichen Code ähnlich der Antwort in der unteren Verknüpfung. das ist ziemlich viel, was den Fehler produziert - ich verstehe, warum ich den Fehler bekomme, ich brauche nur eine Strategie, um es zu überwinden – KerSplosh

+0

Blick auf den Mitgliedsnamen, wenn es ein "enthält." ... wenn ja, können Sie die Punkte teilen und verschachtelte MemberExpressions machen ... https://msdn.microsoft.com/en-us/library/system.linq.expressions.memberexpression(v=vs.110). aspx – DarkSquirrel42

Antwort

2

Der Beispielcode alle Datentypen in Ihrem Szenario umfasst nicht, es ist so schwer zu sagen, wo es genau bricht, aber von der Ausnahme System.Func'2[Transaction,System.Boolean]' and 'System.Boolean ist es offensichtlich, dass auf der linken Seite haben Sie einen Delegaten, der Transaction übernimmt und bool (Func<Transaction, bool>) zurückgibt, und auf der rechten Seite haben Sie nur bool.

Es ist nicht möglich, Func<Transaction, bool>-bool zu vergleichen, aber es ist möglich, die Funktion aufrufen und das Ergebnis vergleichen:

Func<Transaction, bool> func = ...; 
bool comparand = ...; 
Transaction transaction = ...; 
if (func(transaction) == comparand) { ... } 

Was Ausdrucksbaum übersetzt:

Expression funcExpression = ... /*LambdaExpression<Func<Transaction,bool>>*/; 
Expression comparandExpression = Expression.Constant(true); 
Expression transactionArg = /*e.g.*/Expression.Constant(transaction); 
Expression funcResultExpression = Expression.Call(funcExpression, "Invoke", null, transactionArg); 
Expression equalityTestExpression = Expression.Equal(funcResultExpression, comparandExpression); 
+0

Danke für deine Antwort @Serge Semenov aber wenn man bedenkt, dass Bool nur ein Beispiel ist, könnte es irgendeinen Typ geben - wie mache ich den funcExpression generisch zum Vergleich? – KerSplosh

+0

In diesem Fall kann es nicht nur generisch sein, weil Sie ein Eingabeargument für die Funktion - die Transaktion - haben, die von irgendwo kommen sollte. Wenn ich eine Standalone-Konsolen-App zusammenstellen kann, die kompiliert werden kann und das Problem anzeigt, kann ich dabei helfen. –

+0

Ich werde sehen, was ich tun kann. Ich denke, ich muss überdenken, da ich über diesen Code schon ziemlich verwirrt bin – KerSplosh