Search code examples
c#entity-frameworklambdaexpressionexpressionvisitor

Create Arithmetic Formula using ExpressionVisitor


I am trying to create a dynamic formula via entity framework lambda from the columns of a model

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. Am i on the right path to use expressionvisitor?

Any help would be appreciated.


Solution

  • 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.
      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)".
      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.
      Your reordered token's are now "Variable (A), Variable (B), Operator (+), Variable (C), Variable (*).
    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:

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