Quickly Build a Business Rules Engine using C# and Lambda Expression Trees

Posted: August 12, 2015 in .NET Architecture, Binary Tree, BRE, Business Rules Engine, C#, Expression Trees, Lambda Expression Trees, Lambda Queries, LINQ, Microsoft DLR
Tags:

ColeFrancisBizRulesEngine

Author: Cole Francis, Architect

BACKGROUND

Over the past couple of days, I’ve pondered the possibility of creating a dynamic business rules engine, meaning one that’s rules and types are conjured up and reconciled at runtime. After reading different articles on the subject matter, my focus was imparted to the Microsoft Dynamic Language Runtime (DLR) and Labmda-based Expression Trees, which represent the factory methods available in the System.Linq.Expressions namespace and can be used to construct, query and validate relationally-structured dynamic LINQ lists at runtime using the IQueryable interface. In a nutshell, the C# (or optionally VB) compiler allows you to construct a list of binary expressions at runtime, and then it compiles and assigns them to a Lambda Tree data structure. Once assigned, you can navigate an object through the tree in order to determine whether or not that object’s data meets your business rule criteria.

AFTER SOME RESEARCHING

After reviewing a number of code samples offered by developers who have graciously shared their work on the Internet, I simply couldn’t find one that met my needs. Most of them were either too strongly-typed, too tightly coupled or applicable only to the immediate problem at hand. Instead, what I sought was something a little more reusable and generic. So, in absence of a viable solution, I took a little bit of time out of my schedule to create a truly generic prototype of one. This will be the focus of the solution, below.

THE SOLUTION

To kick things off, I’ve created a Expression Trees compiler that accepts a generic type as an input parameter, along with a list of dynamic rules. Its job is to pre-compile the generic type and dynamic rules into a tree of dynamic, IQueryable Lambda expressions that can validate values in a generic list at runtime. As with all of my examples, I’ve hardcoded the data for my own convenience, but the rules and data can easily originate from a data backing store (e.g. a database, a file, memory, etc…). Regardless, shown in the code block below is the PrecompiledRules Class, the heart of my Expression Trees Rules Engine, and for your convenience I’ve highlighted the line of code that performs the actual Expression Tree compilation in blue):


using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ExpressionTreesRulesEngine.Entities;

namespace ExpressionTreesRulesEngine
{
    /// Author: Cole Francis, Architect
    /// The pre-compiled rules type
    /// 
    public class PrecompiledRules
    {
        ///
        /// A method used to precompile rules for a provided type
        /// 
        public static List<Func<T, bool>> CompileRule<T>(List<T> targetEntity, List<Rule> rules)
        {
            var compiledRules = new List<Func<T, bool>>();

            // Loop through the rules and compile them against the properties of the supplied shallow object 
            rules.ForEach(rule =>
            {
                var genericType = Expression.Parameter(typeof(T));
                var key = MemberExpression.Property(genericType, rule.ComparisonPredicate);
                var propertyType = typeof(T).GetProperty(rule.ComparisonPredicate).PropertyType;
                var value = Expression.Constant(Convert.ChangeType(rule.ComparisonValue, propertyType));
                var binaryExpression = Expression.MakeBinary(rule.ComparisonOperator, key, value);

                compiledRules.Add(Expression.Lambda<Func>(binaryExpression, genericType).Compile());
            });

            // Return the compiled rules to the caller
            return compiledRules;
        }
    }
}


As you can see from the code above, the only dependency in my Expression Trees Rules Engine is on the Rule Class itself. Naturally, I could augment the Rule Class to the PreCompiledRules Class and eliminate the Rule Class altogether, thereby eliminating all dependencies. However, I won’t bother with this for the purpose of this demonstration. But, just know that the possibility does exist. Nonetheless, shown below is the concrete Rule class:


using System;
using System.Linq.Expressions;

namespace ExpressionTreesRulesEngine.Entities
{
    ///
    /// The Rule type
    /// 
    public class Rule
    {
        ///
        /// Denotes the rules predictate (e.g. Name); comparison operator(e.g. ExpressionType.GreaterThan); value (e.g. "Cole")
        /// 
        public string ComparisonPredicate { get; set; }
        public ExpressionType ComparisonOperator { get; set; }
        public string ComparisonValue { get; set; }

        /// 
        /// The rule method that 
        /// 
        public Rule(string comparisonPredicate, ExpressionType comparisonOperator, string comparisonValue)
        {
            ComparisonPredicate = comparisonPredicate;
            ComparisonOperator = comparisonOperator;
            ComparisonValue = comparisonValue;
        }
    }
}


Additionally, I’ve constructed a Car class as a test class that I’ll eventually hydrate with data and then inject into the compiled Expression Tree object for various rules validations:


using System;
using ExpressionTreesRulesEngine.Interfaces;

namespace ExpressionTreesRulesEngine.Entities
{
    public class Car : ICar
    {
        public int Year { get; set; }
        public string Make { get; set; }
        public string Model { get; set; }
    }
}


Next, I’ve created a simple console application and added a project reference to the ExpressionTreesRulesEngine project. Afterwards, I’ve included the following lines of code (see the code blocks below, paying specific attention to the lines of code highlighted in orange) in the Main() in order to construct a list of dynamic rules. Again, these are rules that can be conjured up from a data backing store at runtime. Also, I’m using the ICar interface that I created in the code block above to compile my rules against.

As you can also see, I’m leveraging the out-of-box LINQ.ExpressionTypes enumerates to drive my conditional operators, which is in part what allows me to make the PreCompiledRules class so generic. Never fear, the LINQ.ExpressionTypes enumeration contains a plethora of node operations and conditional operators (and more)…far more enumerates than I’ll probably ever use in my lifetime.


List<Rule> rules = new List<Rule> 
{
     // Create some rules using LINQ.ExpressionTypes for the comparison operators
     new Rule ( "Year", ExpressionType.GreaterThan, "2012"),
     new Rule ( "Make", ExpressionType.Equal, "El Diablo"),
     new Rule ( "Model", ExpressionType.Equal, "Torch" )
};

var compiledMakeModelYearRules= PrecompiledRules.CompileRule(new List<ICar>(), rules);


Once I’ve compiled my rules, then I can simply tuck them away somewhere until I need them. For example, if I store my compiled rules in an out-of-process memory cache, then I can theoretically store them for the lifetime of the cache and invoke them whenever I need them to perform their magic. What’s more, because they’re compiled Lambda Expression Trees, they should be lightning quick against large lists of data. Other than pretending that the Car data isn’t hardcoded in the code example below, here’s how I would otherwise invoke the functionality of the rules engine:


// Create a list to house your test cars
List cars = new List();

// Create a car that's year and model fail the rules validations      
Car car1_Bad = new Car { 
    Year = 2011,
    Make = "El Diablo",
    Model = "Torche"
};
            
// Create a car that meets all the conditions of the rules validations
Car car2_Good = new Car
{
    Year = 2015,
    Make = "El Diablo",
    Model = "Torch"
};

// Add your cars to the list
cars.Add(car1_Bad);
cars.Add(car2_Good);

// Iterate through your list of cars to see which ones meet the rules vs. the ones that don't
cars.ForEach(car => {
    if (compiledMakeModelYearRules.TakeWhile(rule => rule(car)).Count() > 0)
    {
        Console.WriteLine(string.Concat("Car model: ", car.Model, " Passed the compiled rules engine check!"));
    }
    else
    {
        Console.WriteLine(string.Concat("Car model: ", car.Model, " Failed the compiled rules engine check!"));
    }
});

Console.WriteLine(string.Empty);
Console.WriteLine("Press any key to end...");
Console.ReadKey();


As expected, the end result is that car1_Bad fails the rule validations, because its year and model fall outside the range of acceptable values (e.g. 2011 < 2012 and 'Torche' != 'Torch'). In turn, car2_Good passes all of the rule validations as evidenced in the pic below:

TreeExpressionsResults

Well, that’s it. Granted, I can obviously improve on the abovementioned application by building better optics around the precise conditions that cause business rule failures, but that exceeds the intended scope of my article…at least for now. The real takeaway is that I can shift from validating the property values on a list of cars to validating some other object or invoking some other rule set based upon dynamic conditions at runtime, and because we’re using compiled Lambda Expression Trees, rule validations should be quick. I really hope you enjoyed this article. Thanks for reading and keep on coding! 🙂

Advertisements
Comments
  1. Nice article, though I would love to see your take on instrumenting the rules so that you can see them execute in a log (which is how I interpret your phrase “building better optics”). While you say that you can “obviously improve” it in this way, I don’t find it obvious at all.

    Liked by 1 person

    • David says:

      Thanks for the code. I was interesting in trying out your implementation but the code cannot compile due to the following line: compiledRules.Add(Expression.Lambda(binaryExpression, genericType).Compile());

      The error is: Severity Code Description Project File Line
      Error CS0305 Using the generic type ‘Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult>’ requires 10 type arguments ExpressionTreesRulesEngine c:\users\ddavi\documents\visual studio 2015\Projects\ExpressionTreesRulesEngine\ExpressionTreesRulesEngine\PreCompiledRules.cs 30

      The error is in the immediately following the Express.Lambda.. what’s the fix?

      Like

      • colefrancis says:

        Make sure you have the .NET 4 Framework (or greater) installed on your compiling machine, and also verify that both projects in the solution are targeting the .NET 4 Framework (or later). Also, make sure that all the project references are resolving correctly. Other than the .NET Framework, I don’t think I’m doing anything special. Thanks for checking out my post, and good luck! Let me know how it goes.

        Like

      • Bob says:

        I had the same issue. This was the fix:

        compiledRules.Add(Expression.Lambda<Func<T, bool>>(binaryExpression, genericType).Compile());

        Liked by 1 person

    • colefrancis says:

      Thanks for the feedback, Robert. By “better optics”, I was merely referring to the ability of identifying individual rule breaches within larger rule sets. Perhaps this level of granularity was what you were referring to as well? Unfortunately, I’m only mildly intrigued by the possibility of taking this example to the next level at the present moment. But, keep checking back. I might surprise you.

      Like

  2. talaa says:

    Hey thanks for the great article. I was trying to build the similar expression tree from a text rule. So for example user will input:
    “If Car Year Is Greater Than “2012” And Car Make Is Equal To “El Diablo” And Car Model Is Equal To “Torch” Then DisplayError(“Rule validation has failed!”)”
    and I will create the similar rule out of it. Of course I need a compiler (i.e. Scanner, Parser) to do this. Would you like to share how you would approach this scenario?
    Thanks!

    Like

  3. Jürgen Bayer says:

    Thanks for the great article. Helped me a lot. A little correction (even with .NET 4.6): compiledRules.Add(Expression.Lambda(binaryExpression, genericType).Compile());
    must be
    compiledRules.Add(Expression.Lambda<Func<T, bool>>(binaryExpression, genericType).Compile());
    just because Func needs at least one type parameter and compiledRules is a List<Func<T, bool>>.

    And by the way: You forgot the ICar interface.

    Like

  4. Deepak Joshi says:

    Great article!!!

    I tried using the code, but getting below error at line ” compiledRules.Add(Expression.Lambda(binaryExpression, genericType).Compile());”

    Error:
    Error 1 Using the generic type ‘System.Func’ requires 1 type arguments ~\ExpressionTreesRulesEngine\PrecompiledRules.cs 30 53 ExpressionTreesRulesEngine

    Like

  5. colefrancis says:

    Thanks for the positive feedback. I’m glad everyone enjoyed this one, and special thanks goes out to Jürgen Bayer and others for posting the fix. You guys rock! Thanks for reading.

    Like

  6. JT says:

    Cole, great article. Do you know how this can be modified to use a Dictionary<string, Object>() vs a strongly typed object? ex:

        var option = new Dictionary<string, Object>();
        option.Add("Color", "Red");
        option.Add("Text", "Abort");
        option.Add("Language", "English");
    
        var rules = new List<Rule> {
            new Rule("Color", ExpressionType.Equal, "Red"),
            new Rule("Text", ExpressionType.Equal, "Abort")
        };
    
        var compiledRules = CompileRule(new List<Dictionary<string, Object>>(), rules);
    

    Basically, I need to validate rules from an object generated during runtime based off of user input. I also tried using a dynamic object, but the CompileRule function is unable to find the property (due to T being dynamic). Any guidance would be appreciated.

    Like

  7. jthope says:

    Cole, great article. Do you know how this can be modified to use a Dictionary() vs a strongly typed object? ex:

    var option = new Dictionary();
    option.Add(“Color”, “Red”);
    option.Add(“Text”, “Abort”);
    option.Add(“Language”, “English”);

    var rules = new List {
    new Rule(“Color”, ExpressionType.Equal, “Red”),
    new Rule(“Text”, ExpressionType.Equal, “Abort”)
    };

    var compiledRules = CompileRule(new List(), rules);

    Basically, I need to validate rules from an object generated during runtime based off of user input. I also tried using a dynamic object, but the CompileRule function is unable to find the property (due to T being dynamic). Any guidance would be appreciated.

    Like

  8. ccerrato147 says:

    Good article. Thanks for sharing!

    Like

  9. […] Expression Trees and Dynamic Business Rules […]

    Like

  10. […] Expression Trees and Dynamic Business Rules […]

    Like

Your Feedback is Welcome

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s