Building a rule engine in c# (part 8: extending the rule engine with method calls of the objects)

In a comment on a blog article (https://netmatze.wordpress.com/2014/09/17/building-a-rule-engine-in-c-part-7-extending-the-rule-engine-with-a-like-operator/) about the rule engine project (ruleengine.codeplex.com) Matt wrote that he would need the feature that the makes it possible to call methodes at the object and check for the result of that method calls. So i made some changes to the rule engine project to make that possible. I released a new version at codeplex and here you can see a test that uses that method calls.

[TestMethod]
public void SimpleExpressionEvaluatorWasEmployedDateMethod()
{
    Person person1 = new Person()
    {
        Name = "Mathias",
        Age = 36,
        Children = 2,
        Married = true,
        Birthdate = new DateTime(1976, 5, 9),
        EmployDate = new DateTime(2012,12,1)
    };
    Person person2 = new Person()
    {
        Name = "Anna",
        Age = 32,
        Children = 2,
        Married = false,
        Birthdate = new DateTime(2002, 2, 2),
        EmployDate = new DateTime(2013, 12, 1)
    };
    Person person3 = new Person()
    {
        Name = "Karo",
        Age = 38,
        Children = 2,
        Married = true,
        Birthdate = new DateTime(1976, 2, 2),
        EmployDate = new DateTime(2011, 12, 1)
    };
    Evaluator evaluator = new Evaluator();
    var result1 = evaluator.Evaluate(" (CalculateAge() >= 10 && Married = true) 
&& WasEmployed('2013-12-01') = true ", person1);
    Assert.AreEqual(result1, true);
    var result2 = 
    evaluator.Evaluate(" CalculateAge() >= 10 && Married = true ||     
    WasEmployed('2014-12-01') = false ", person2);
    Assert.AreEqual(result2, false);
    var result3 = evaluator.Evaluate(
    " (CalculateAge() >= 2 || Married = true 
    || WasEmployed('2010-12-01') = true) then SetCanReceiveBenefits(true) 
    else SetCancelBenefits(true) ", person3);
    Assert.AreEqual(result3, true);
    Assert.AreEqual(person3.CancelBenefits, false);
    Assert.AreEqual(person3.ReceiveBenefits, true);
}
Advertisements

19 Comments on “Building a rule engine in c# (part 8: extending the rule engine with method calls of the objects)”

  1. Anders says:

    Super Clean and nice implementation! Thanks for all your good work and effort. Its highly appreciated and is an inspiration to me and many other people in the community.

  2. Osub says:

    Great rule engine. Thanks for sharing.
    Is there possibility define rules for nested object properties like “Address.City …” using dot notation?

  3. Osub says:

    Sorry for repeat but some piece is missing from my question.
    Is there possibility define rules for nested object properties like Address.City using dot notation?

    • netmatze says:

      Hallo Osub,

      I am very sorry but that feature (a dot notation for object.method) is not implemented in the rule engine.

      Greetings
      netmatze

  4. lihan says:

    I appreciate your clean codes and clear explanation. I wonder if it can extend the rule engine to evaluate against a generic dictionary so that each evaluation returns a pre-defined scale (as in the Likert scale in surveys) for a property of type TValue to be evaluated against a list of K (TKey)?

    • netmatze says:

      Hello lihan,

      I have troubles to understand what you exactly need. You want to evaluate a dictionary of type TKey, TValue. So each dictionary entry holds what, and which part should be evaluated against what rule?
      If you could clearify your question or give me a test with the call you would like to make, maybe i could help you.

      Greetings
      netmatze

      • lihan says:

        Thank you for the quick responding. My question is how to extend the rule engine so that for each source or property of an object, a rating of integer value can be returned. For example, we can evaluate the value against a dictionary of such that if the temperature is less than 32, return 1, if temp is between 50.1 and 80, return 2, if temp is greated than 104, return 4, and so on.So each property is rated based on a user-defined rule dictionary or a set of KeyValuePairs. Does this make sense?

      • netmatze says:

        Hello lihan,

        I would implement it the following way but i am not sure if that is what you need.
        [TestMethod]
        public void SimpleTemperatureTestMethod()
        {
        Temperature temperature = new Temperature()
        {
        TemperatureValue1 = 10.2,
        TemperatureValue2 = 30.5
        };
        Evaluator evaluator = new Evaluator();
        var result = TemperatureCalculator(temperature);
        Assert.AreEqual(result, 1);
        }
        private int TemperatureCalculator(Temperature temperature)
        {
        Evaluator evaluator = new Evaluator();
        var result1 = evaluator.Evaluate
        (” TemperatureValue1 50.1 and TemperatureValue1 104″, temperature);
        if (result3)
        return 4;
        return 0;
        }

        I checked it in so if you checkout the newest source code version you can try it.

        greetings
        netmatze

  5. Hi netmatze,

    You’re really a great people to do this.

    I just wonder about if the expression rule can be defined as nested if, let’s see my below example:

    If ( age > 15 )
    then
    if(married = true)
    then
    DoFunctionA()
    else
    DoFunctionB()
    else
    DoFunctionC()

    Thanks you alot about this masterpiece

    • netmatze says:

      Hello valentinonguyen,

      I changed some parts in the ruleengine and now you can do what you want to do. So with the new Version 1.6 i released today it should work.
      Here is the test that shows how it could work:

      Person person3 = new Person()
      {
      Name = “Karo”,
      Age = 38,
      Children = 2,
      Married = true,
      Birthdate = new DateTime(1976, 2, 2),
      EmployDate = new DateTime(2011, 12, 1)
      };
      Evaluator evaluator = new Evaluator();
      var result4 = evaluator.Evaluate(
      ” (Age > 30) then (Married = false) then SetCanReceiveBenefits(true) else SetCancelBenefits(true) else SetStopBenefits(true) “, person3);
      Assert.AreEqual(result4, false);
      Assert.AreEqual(person3.CancelBenefits, true);
      Assert.AreEqual(person3.ReceiveBenefits, false);
      Assert.AreEqual(person3.StopBenefits, false);

      you can see the test SimpleExpressionEvaluatorWasEmployedDateMethod in the source code.

      I hope that helps

      greetings
      netmatze

  6. Johan says:

    Hi Netmatze,

    Can you please indicate if it is possible to pass a variable into a rule.

    E.g. Rule expressionRule = new Rule(” (GetSalary()/2) > 100 then SetAge(45) else SetAge(22)”);

    I want to replace SetAge(45) with SetAge(x) where x can be a function call or an expression
    e.g. x = Children * 3;

    Regards

    • netmatze says:

      Hello Johan,

      I am very sorry but that functionality is not included in the rule engine. I am thinking about implementing a rule engine 2.0 to include such things like using variables in rules and refactoring some parts of the rule engine but i dont know when i will have time to do that.

      Greetings
      netmatze

  7. David says:

    Just wanted to say thank you for the updates and taking suggestions to constantly improve. Love it

  8. David says:

    BTW, one request….

    The ability to invoke a method to set a value is great but if you have a class with a lot of properties that need evaluated and possibly ‘fixed’, that could get messy real fast…

    Is it possible to make a modification to set properties like this instead ?

    Person person1 = new Person()
    {
    Name = “Mathias”,
    Age = 36,
    Children = 2,
    Married = true,
    Birthdate = new DateTime(1976, 5, 9),
    CancelBenefits = false,
    ReceiveBenefits = false
    };
    Evaluator evaluator = new Evaluator();

    bool result = evaluator.Evaluate(
    ” (Age < 10) then CancelBenefits = false else CancelBenefits = true ", person1);
    Assert.AreEqual(person1.CancelBenefits, true);

    • netmatze says:

      To answer your question, it is possible to change the set property syntax (CancelBenefits = false), but it is not that easy and i can not tell you when i will have time to make that change.

      greetings
      netmatze

      • David says:

        I see, thanks for the reply. I will keep following it, great work!

  9. Elias Chatzigeorgiou says:

    Hi Netmatze,

    many thanks for the great article. I am currently working on a complex data manipulation
    project that reads from CSV files into in-memory LINQ collections and then performs
    complex queries upon them to calculate price based on specific rules.

    I tried using your library to improve LINQ queries, but seems like I am facing a bug.
    Specifically, I have build a multiple column index like the below:

    _IndexedDatamixCol = _datamixCol
    .CreateIndexKey(c => c.Bookable_from)
    .AddIndexKey(c => c.Bookable_to)
    .AddIndexKey(c => c.Min_stay)
    .AddIndexKey(c => c.Max_stay)
    .AddIndexKey(c => c.HotelID)
    .AddIndexKey(c => c.RoomType)
    .AddIndexKey(c => c.RoomTypeCode)
    .AddIndexKey(c => c.RoomViewCode)
    .AddIndexKey(c => c.RoomLocationCode)
    .AddIndexKey(c => c.Room_type_and_meal_code);

    but when I try to run some queries upon it, I am facing a null reference exception in
    the DoubleRightRotation / DoubleLeftRotation functions.

    I tried to patch them as below:

    public void DoubleRightRotation(FastTreeNode treeNode)
    {
    FastTreeNode right = treeNode.RightTreeNode;
    FastTreeNode rightLeft = right.LeftTreeNode;
    FastTreeNode parent = treeNode.ParentTreeNode;
    //elias: was
    //FastTreeNode rightLeftLeft = rightLeft.LeftTreeNode;
    //FastTreeNode rightLeftRight = rightLeft.RightTreeNode;
    //elias: new
    FastTreeNode rightLeftLeft = rightLeft != null ? rightLeft.LeftTreeNode : null;
    FastTreeNode rightLeftRight = rightLeft != null ? rightLeft.RightTreeNode : null;

    but then I got further null references like:

    leftRight.ParentTreeNode = parent;

    where leftRight object is null.

    Any help would be appreciated. If you wish I can share the entire project and data
    files so you can check in detail.

    Thanks

  10. murugan says:

    Thanks for your good work. My doubt is if i want to implement the AND OR condition in expression tree. how to do?
    for ex

    (AGE>20 && NAME=”MGAN”)||
    (AGE>60)

    in the above case how to write linq expression ?


If you have a note or a question please write a comment.

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