简体   繁体   English

使用ExpressionVisitor创建算术公式

[英]Create Arithmetic Formula using ExpressionVisitor

I am trying to create a dynamic formula via entity framework lambda from the columns of a model 我正在尝试从模型的列通过实体框架lambda创建动态公式

public class OutputModel
{
   public decimal Result {get;set;}
}

public class TableTest
{
  public decimal A {get;set;}
  public decimal B {get;set;}
  public decimal C {get;set;}
}

Expression<Func<TableTest, OutputModel>> _expr = t => new OutputModel();

TestExpressionVisitor _visitor = new TestExpressionVisitor();
_visitor.Visit(_expr);

var _result = new TempDataContext().TableTests.Select(_expr);

I was thinking of using a expression visitor to modify the result 我当时在考虑使用表达式访问者来修改结果

public class TestExpressionVisitor : ExpressionVisitor
{
    public override Expression Visit(Expression node)
    {
        return base.Visit(node);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        return base.VisitMember(node);
    }
}

But not really sure how to construct an Expression that can do the arithmetic function for column from a string parameter ( {A}+{B}({C}) ) where A,B and C coming from TableTest and will put the result on OutputModel.Result . 但不是很确定如何构造一个表达式,该表达式可以从字符串参数( {A} + {B}({C}) )进行列的算术运算,其中A,B和C来自TableTest,并将结果放在OutputModel.Result Am i on the right path to use expressionvisitor? 我在使用Expressionvisitor的正确路径上吗?

Any help would be appreciated. 任何帮助,将不胜感激。

To expand on my comment, in general you would need to: 要扩展我的评论,通常需要:

  1. Parse your input and convert it to tokens, where a token is just a semantic component of your string transformed into a C# object. 解析您的输入并将其转换为令牌,其中令牌只是转换为C#对象的字符串的语义成分。
    If we use the example (A+B)*C the tokens would be the objects "Bracket (open), Variable (A), Operator (+), Variable B, Bracket (Close), Operator (*), Variable (C)". 如果我们使用示例(A+B)*C则标记将是对象“括号(打开),变量(A),运算符(+),变量B,括号(关闭),运算符(*),变量(C )”。
    I won't go into detail how to do this, because it's not part of the question, but I once wrote a very simple but powerful tokenizer using sequences of regular expressions, because these already do most of the hard work required in parsing. 我不会详细介绍如何执行此操作,因为这不是问题的一部分,但是我曾经使用正则表达式序列编写了一个非常简单但功能强大的令牌生成器,因为它们已经完成了解析过程中所需的大部分工作。
  2. Reorder your tokens like they need to be processed, considering operator precedence rules and bracketing. 考虑操作符优先级规则和括号,对令牌进行重新排序以使其需要处理。 This can be done by using Dijkstra's shunting-yard algorithm, see https://en.wikipedia.org/wiki/Shunting-yard_algorithm for an example. 这可以通过使用Dijkstra的shunting-yard算法来完成,有关示例,请参见https://en.wikipedia.org/wiki/Shunting-yard_algorithm
    Your reordered token's are now "Variable (A), Variable (B), Operator (+), Variable (C), Variable (*). 您重新排序的令牌现在为“变量(A),变量(B),运算符(+),变量(C),变量(*)。
  3. Transform your token tree or queue (however you choose to store this) to an expression tree. 将令牌树或队列(无论如何选择存储)转换为表达式树。

In case of a queue of tokens the last step would be: 在令牌队列的情况下,最后一步是:

  // for demo purposes I manually fill the list of tokens
  // with the tokens in order how they are output by the shunting-yard algorithm
  var tokenQueue = new Token[]
  {
    new VariableToken("A"),
    new VariableToken("B"),
    new OperatorToken("+"),
    new VariableToken("C"),
    new OperatorToken("*")
  };
  var inputParameter = Expression.Parameter(typeof(TableTest));
  var expressions = new Stack<Expression>();
  foreach (var token in tokenQueue)
  {
    // transform token to expression by using the helper methods of https://msdn.microsoft.com/de-de/library/system.linq.expressions.expression_methods(v=vs.110).aspx
    switch (token)
    {
      case VariableToken variableToken:
        // this will reference the property in your TableTest input specified by the variable name, e.g. "A" will reference TableTest.A
        expressions.Push(Expression.Property(inputParameter, variableToken.Name));
        break;
      case OperatorToken operatorToken:
        // This will take two expression from the stack, give these to input to an operator and put the result back onto the queue for use for the next operator
        var rightOperand = expressions.Pop();
        var leftOperand = expressions.Pop();
        if (operatorToken.Name == "+")
        {
          expressions.Push(Expression.Add(leftOperand, rightOperand));
        }
        else if (operatorToken.Name == "*")
        {
          expressions.Push(Expression.Multiply(leftOperand, rightOperand));
        }
        break;
    }
  }

  // create and fill output model with final expression
  var outputModelExpr = Expression.New(typeof(OutputModel).GetConstructor(new[] {typeof(decimal) }), expressions.Single());

  // create the lambda expression 
  // in this example it will have the form: x => return new OutputModel((x.A + x.B) * x.C)
  Expression<Func<TableTest, OutputModel>> lambda = Expression.Lambda<Func<TableTest, OutputModel>>(outputModelExpr, inputParameter);

  // only for testing purposes: compile it to a function and run it
  var calc = lambda.Compile();
  var testInput = new TableTest { A = 1, B = 2, C = 3 };
  Console.WriteLine(calc(testInput).Result); // returns 9, because (A + B) * C = (1 + 2) * 3 = 9

With the token classes: 使用令牌类:

  public abstract class Token
  {
    public string Name { get; protected set; }
  }

  public class VariableToken : Token
  {
    public VariableToken(string name) { Name = name; }
  }

  public class OperatorToken : Token
  {
    public OperatorToken(string name) { Name = name; }
  }

Please note I added a constructor to OutputModel, because this makes the expression much easier: 请注意,我向OutputModel添加了一个构造函数,因为这使表达式更加容易:

public class OutputModel
{
   public OutputModel(decimal result) { Result = result; }

   public decimal Result {get;set;}
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM