【问题标题】:Evaluating complex expression tree评估复杂的表达式树
【发布时间】:2017-01-16 11:56:55
【问题描述】:

我有一个基本规则引擎,我构建的方式与此处建议的路线非常相似:

How to implement a rule engine?

我已经根据进一步的要求对其进行了扩展,现在我需要评估复杂的类,例如

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

最基本形式的代码是:

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);    

为了评估复杂的类,我使用了类似于这里的方法:

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

我遇到的问题是,当我尝试使用类 (Transaction.IsOpen) 的属性评估规则时,我使用表达式右侧的根类型的类型得到它,但类型表达式左侧的复杂对象。

这会导致错误:

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

我该如何克服这个问题?我不是使用表达式树的专家,当示例偏离标准文档时,许多概念被证明难以掌握。

编辑:这是代码(我省略了一些特定于环境的内容,以便关注问题)

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);
    }
}

【问题讨论】:

  • 您当前的代码现在看起来如何?
  • 我的基本代码中的左侧赋值基本上使用了类似于底部链接中答案的代码。这几乎就是产生错误的原因 - 我明白我为什么会收到错误,我只需要一个克服它的策略
  • 查看成员名是否包含“.” ...如果是这样,您可以按点拆分并制作嵌套的 MemberExpressions ...msdn.microsoft.com/en-us/library/…
  • 谢谢 - 这就是我正在做的 - 问题是类型不匹配。但是我没有使用成员表达式,因此将探索该选项
  • 实际上 - 我已经尝试过了(请参阅第二个链接),这仍然是我的问题所在。我应该对 MemberExpression 做些什么特别的事情吗?

标签: c# expression-trees


【解决方案1】:

示例代码并未涵盖您的场景中使用的所有数据类型,因此很难准确判断它在哪里中断,但从异常 System.Func'2[Transaction,System.Boolean]' and 'System.Boolean 可以明显看出,在左侧您有一个接受 Transaction 的委托并返回bool (Func&lt;Transaction, bool&gt;),而右手边只有bool

无法将Func&lt;Transaction, bool&gt;bool 进行比较,但可以调用函数并比较其结果:

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

翻译成表达式树的内容:

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);

【讨论】:

  • 感谢您的回答@Serge Semenov,但考虑到 bool 只是一个示例,它可以是任何类型 - 如何使 funcExpression 通用以进行比较?
  • 在这种情况下,它不能只是通用的,因为你有一个函数的输入参数——事务——它应该来自某个地方。如果可以组装一个可以编译并显示问题的独立控制台应用程序,我可以提供帮助。
  • 我会看看我能做什么。我想我需要重新考虑,因为我已经对这段代码感到很困惑,因为它是
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多