This project has moved. For the latest updates, please go here.

Nested Expressions with Complex / Conditional Logic

May 13, 2015 at 1:51 PM
I just found your library and am definitely seeing it's power for being able to assign rules outside of code and am loving it - THANK YOU!!!

My current situation is that I'm trying to create a rule engine that could dynamically assign values for multiple records (up to 50,000) based upon set rules that can be configured outside of the codebase.
I followed your conversation with @sharewithkinjal on this link and am getting close to understanding how to do this, but my challenge / difference is that I have conditional criteria for each member I'm setting within my scope.

So, for example, in your posted solution, you had something along the lines of these 3 statements:
            var scope = new Scope();
            var ee = new ExpressionExtension<Scope, int>(scope);
            ee.Eval("A = 10");
            ee.Eval("B = A + 10");
            ee.Eval("C = A + B");
Since my situation requires more complex logic, I'd also like to see if I could do the same with something along the lines of:
            ee.Eval("if(A == 10) D = 15; else {D = 20;}");
Which, when marked with comp.ExpressionType = CompiledExpressionType.StatementList, it creates the expression:
.Block() {
    .If ($scope.A == 10) {
        $scope.D = 15
    } .Else {
        .Block() {
            $scope.D = 20
        }
    }
}
Which obviously breaks the ability to get the left-hand easily.

I feel like this should be able to be done, but am hoping you have some ideas as to how / where to point me... ANY help would really be greatly appreciated!!!

Thank you again for your awesome work and library!!!
Developer
May 15, 2015 at 11:55 PM
Hi!

I'm not sure what you need to do. Do you mean you want to do something like:
ee.Eval("D = (A == 10) ? 15 : 20");
Also, a block statement returns the value of the last expression in the block. the return statement is not implemented yet, but if you really needed to use a statement list and return a value, you can do something like this:
ee.Eval("if(A == 10) D = 15; else {D = 20;} D;");
which should compile to the following expression:
.Block() {
    .If ($scope.A == 10) {
        $scope.D = 15
    } .Else {
        .Block() {
            $scope.D = 20
        }
    }
    $scope.D
}
again, I'm not really sure what you need to do.
May 16, 2015 at 2:47 PM
Hi Rupert!!!

Thank you so much for your reply!!! I apologize for not managing to convey my situation well enough.

Basically, in the thread mentioned above, you worked a solution for the situation where you have something like:
  • "A = 20"
  • "B = A + 10"
  • "C = A + B"
Where each expression will have the left side stored in a dictionary key and the expression stored as the value, so when you reach a situation where C needs the values of A & B, it gets their expressions from the dictionary and the calculations are nested recursively to get the correct values based upon their dependants.

My only question is that this seems to rely on simple expressions (not block / conditional statements) and my situation requres the same kind of ability for the more complex cases of block / conditional and I'm trying to figure out how to update your previous solution to allow for that / see how it would be possible.

Does that make sense? How could I upgrade your previous solution for complex logic - Would a dictionary even still work?

For example (I'm just truly making stuff up to show what a situation could look like):
  • "A = 10"
  • "if ( A<20 ) {B = 2 * A;} else {B = 3;}"
  • "if ( B <= A ) {C = B;} else {C = B - A;}"
  • "D = (2* (A + B) ) - C"
And, finally, a separate question that's now also coming to mind - Given I'll need to fo this for 50,000+ scopes, would there also be a way to implement the expression caching while still keeping the ability to correctly nest the expressions so, for example, C will get A & B's values to be able to calculate itself correctly??

I REALLY hope that makes sense!!!
Developer
May 16, 2015 at 3:31 PM
Yes, I understand now.

This is certainly becoming a rather popular application of ExpressionEvaluator - nested expressions.

There was some talk about this on this thread and this thread and an implementation of it here

Let me know if it helps
May 17, 2015 at 5:03 AM
Hi Rupert,

Thank you - Unfortunately, those are the ones I already found and are still proving very tough to change to suit my needs - I'll definitely be working on it, though, and will post up my code as I work through it. If you have any thoughts / suggestions, I'd OF COURSE love to hear them!

Thanks again for an awesome library and great communication and support!
Developer
May 17, 2015 at 10:56 PM
Edited May 17, 2015 at 10:59 PM
Of course the best way to handle this so that you can use it in complex expressions is to handle it directly during the compilation phase.

I have tried a method where you can place a custom attribute on a property and this will cause Expression Evaluator to emit a call to a Parse function that parses the value of the expression during "run" time. However since the expression is compiled to a delegate type of Func<TScope, TResult> and neither the scope type or result type can be set in a consistent way at that point, the current option is to have the result cast to type object. You will then have to explicitly cast your objects to whatever type they are needed in the expression they are used.

I'm considering adding more support for this sort of feature, since it seems to be a recurring use case, as this is the third time I have seen this sort of request.

I have some ideas:

Create a class ExpressionContainer that contains the string expression and the compiled function. ExpressionEvaluator can check if an object is of type ExpressionContainer, and instead of emitting the object, it will wrap it in an Eval() call so that at execute time the contents will be evaluated. This will make it difficult to manipulate the actual ExpressionContainer in the compiled expression, but I don't think we need to worry about that much.

Create a class similar to Dictionary that will store keys and ExpressionContainers, this can be used as a scope parameter. But with all the logic wrapped around the ExpressionContainer I probably don't have to do anything special.

Expression evaluator already supports fake dynamics, using a dictionary to store object properties.

If you have Git setup, I can push a new branch here that contains support for this new feature. I don't want to officially support it until we have a good idea of how to use it though, so it will be highly experimental.