Building a rule engine in c# (part 3: extending the rule engine to handle aggregations of collections)

In had a comment on my post about building a rule engine in c#. The question in this comment was how to handle aggregations within the rule engine. In this post i try to give one example how to handle aggregations. I will show a possible way to include sum and average in the rule engine and to look if the aggregated property of the property of a collection of objects corresponds one or many of the defined rules. I wrote two post about the topic building a rule engine in c# and building a rule engine in c# (part 2: extending the rule engine to handle collections). Now i will extend the RuleValidator i implemented in the second post with two methods that can handle the sum and average. This are the rules in the database:

ProcessingEngineDatabase

Now i have a list of Person objects and want to know if the sum of all children are greater than zero and the sum of the age of all Person objects is Less than or equal 50. So in that case the result is false because the second rule (Age <= 50) failes.

Person person1 = new Person() { Name = "Mathias", Age = 35, Children = 2 };
Person person2 = new Person() { Name = "Anna", Age = 32, Children = 2 };
RuleValidator ruleValidator = new RuleValidator();
var sum = ruleValidator.ValidateRulesSum(
    new Person[] { person1, person2 }, 
    new Rule[] { firstRule, secondRule });

The ValidateRulesSum method call sums up the values of the property that is relevant for the overtaken rule and then checks the rule with the calculated sum.. To do so i need some reflection and the dynamic keyword to handle different types. (Int32, Double etc.). So thats how the ValidateRulesSum and the ValidateRulesAvg methodes look like.

public bool ValidateRulesSum<T>(IEnumerable<T> values, IEnumerable<Rule> rules)
{
    foreach (var rule in rules)
    {
        // necessary to create the type dynamic so i could use the + operator to 
        // build the sum on an Int32 or Double or Decimal
        dynamic sum = Activator.CreateInstance(
            values.GetType().GetElementType().GetProperty(rule.PropertyName).PropertyType);
        foreach (var value in values)
        {
            dynamic innerValue = value.GetType().
                GetProperty(rule.PropertyName).GetValue(value, null);
            // creating the sum
            sum += innerValue;
        }
        // building the Func
        dynamic func = BuildGenericFunction(rule, sum);
        // checking the rule Func with the sum value
        if (!func(sum))
            return false;                
    }
    return true;
}
public bool ValidateRuleAvg<T>(IEnumerable<T> values, IEnumerable<Rule> rules)
{
    foreach (var rule in rules)
    {
        dynamic sum = Activator.CreateInstance(values.GetType().
            GetElementType().GetProperty(rule.PropertyName).PropertyType);
        var counter = 0;
        foreach (var value in values)
        {
            dynamic innerValue = value.GetType().
                GetProperty(rule.PropertyName).GetValue(value, null);
            sum += innerValue;
            counter++;
        }
        dynamic avg = sum / counter;
        dynamic func = BuildGenericFunction(rule, avg);
        if (!func(avg))
            return false;
    }
    return true;
}
private object BuildGenericFunction(Rule rule, object sum)
{
    ExpressionBuilder expressionBuilder = new ExpressionBuilder();
    System.Type specificType = sum.GetType();
    var param = Expression.Parameter(specificType);
    Expression expression = expressionBuilder.
        BuildExpression(specificType, rule.Operator_, rule.Value, param);
    // creates the generic type so i can make a call with the type of 
    // the property to the BuildLambdaFunc function
    MethodInfo method = this.GetType().GetMethod("BuildLambdaFunc", 
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    MethodInfo generic = method.MakeGenericMethod(specificType);
    object func = generic.Invoke(this, new object[] { expression, param });
    return func;
}

private Func<T, bool> BuildLambdaFunc<T>(
    Expression expression, ParameterExpression param)
{
    // building the lambda function
    Func<T, bool> func = Expression.Lambda<Func<T, bool>>
        (expression, param).Compile();
    return func;
}

The code is straight fourward but to get out the type of the property of the Person object that is defined in the Rule (e.g. Age) i need the MakeGenericMethod method of the MethodInfo type and that i have to use the Invoke method to invoke the dynamic created method. That is necessary because the BuildLambdaFunc method has a generic type T definied and i need to overtake the type of the property of the Person object (e.g. Int32 for Age). So with the reflection method MakeGenericMethod i can build the method with the generic type T. I dont need to know the type at compile time i can find it out at runtime. In the fourth part of this blog series i write about implementing an expression evaluator to define more complex expressions. (Building a rule engine in c# (part 4: extending the rule engine to evaluate defined expressions)) and in the fifth part building a rule engine in c# (part 5: bringing it all together) i give an overview about the usage of the ruleengine.codeplex.com project i created to bring the code parts in that post series together.

Advertisements