Building a rule engine in c# (part 7: extending the rule engine with a like operator)

In a comment on the rule engine project (ruleengine.codeplex.com) Damien wrote that he would need a like operator in the rule engine to make string compare with wildcards possible. So i added that feature to the rule engine. You now can write “Name like ‘mathi%'” and if the Name property of an object contains the string ‘mathias’ it would return that object. Here is a test that shows the cases that are now possible with the rule engine.

[TestMethod]
public void SimpleExpressionLikeMethod()
{
    Person person1 = new Person()
    {
        Name = "mathias",
        Age = 36,
        Children = 2,
        Married = true,
        Birthdate = new DateTime(1976, 05, 09),
        CancelBenefits = false,
        ReceiveBenefits = false
    };
    Person person2 = new Person()
    {
        Name = "anna",
        Age = 32,
        Children = 2,
        Married = false,
        Birthdate = DateTime.Now,
        CancelBenefits = false,
        ReceiveBenefits = false
    };
    Evaluator evaluator = new Evaluator();
    var result1 = evaluator.Evaluate<Person>(
        " Name like 'math%' ", person1);
    Assert.AreEqual(result1, true);
    var result2 = evaluator.Evaluate<Person>(
        " Name like '?nn?' ", person2);
    Assert.AreEqual(result2, true);
    List<Person> list = new List<Person>() { person1, person2 };
    foreach(var person in list)
    {
        var result = evaluator.Evaluate<Person>(
            " Name like 'mat%' || Name like 'a??a' ", person); 
        if(result)
        {
            Debug.WriteLine(person.Name);
            Assert.AreEqual(result, true);
        }
    }
}

As you see it there are two wildcard that work. The % wildcard can only be used at the end of the text, for example “Name like ‘mat%'”. The ? wildcard can be used in the at any possition, for example “Name like ‘math?ia?'”.


16 Comments on “Building a rule engine in c# (part 7: extending the rule engine with a like operator)”

  1. Anders says:

    Hi,

    First of all thanks for your enlightening and good articles related to the rule engine, I have truly enjoyed reading them.

    I have a question regarding scalability and performance. Have you or anyone else here done some tests related to how well this scale , lets say you have 20.000 rules each containing lets say 5-8 different conditions and we lets say we have 1.000.000 objects to evaluate to see what rules that match each object.

    I’m just checking to see if this has been previously done or if its time for some exploratory research 😉

    Many thanks again for your great and inspiring work /Anders

    • netmatze says:

      Hello Anders,

      Thanks for your kind words, i am very glad that the ruleengine is used so intensive.

      To your question, i have never used the ruleengine for so many rules with that much conditions.
      I would try it with the PreEvaluate method of the Evaluator class.
      That method returns the abstract syntax tree of the rule you want to generate that means you he does not scan and parse the rule string every time.
      You can save that abstract syntax tree for every rule in a list call it for every object, that is the fastest method to use the rule engine but for
      1.000.000 objects it will still take a while. Here is the example how to use the PreEvaluate method.

      Evaluator evaluator = new Evaluator();
      var tuple = evaluator.PreEvaluate(
      ” (Age < 10) then SetCanReceiveBenefits(true) else SetCancelBenefits(true) ");
      evaluator.ExecuteEvaluate(tuple, person1);
      evaluator.ExecuteEvaluate(tuple, person2);

      I hope that helps to solve the performance problem.

      Greetings
      netmatze

  2. Ken Fellows says:

    Hi,
    Is there a way to assign a variable for example “Age set 5 + 6”? Age would get the value of 11.

    Thanks in advance.

    • netmatze says:

      Hello Ken

      I do not know what you want to accomplish.
      The rule engine uses properties of objects to check against defined rules. You can do the following:

      Person person1 = new Person()
      {
      Name = “Mathias”,
      Age = 36,
      Children = 2,
      Married = true,
      Birthdate = new DateTime(1976, 5, 9)
      };
      Evaluator evaluator = new Evaluator();
      var result1 = evaluator.Evaluate(
      ” (Age > 5 + 6) set Age = 12 “, person1);
      Assert.AreEqual(result1, true);
      Assert.AreEqual(person1.Age, 12);

      So it is possible to add two numbers, but the rule engine has no symbol table and you can not define and use variables.

      Greetings netmatze

      • Ken says:

        Hi, thanks for the quick response.
        I have another question. I tried this code:
        Evaluator evaluator = new Evaluator();
        var result1 = evaluator.Evaluate(
        ” (Age < 10) then SetCanReceiveBenefits(true) else SetCancelBenefits(true) ", person1);
        and all I get is the first SetCanReceiveBenefits(true) call even when the Age is greater then 10. Is there a bug in the, then/else code?

  3. Catalin says:

    Hello! Great job on this rule engine. But I have a question, if you please can answer.

    I have a User object, with a “Text” property
    class User{
    string Text {get; set;}
    }

    and a “Dictionary” class, also with a “Text” property, but collection this time.
    class Dictionary {
    List TextCollection { get; set; }
    }

    I need a result with how many words in user.Text were found in collection dictionary.TextCollection, but separately for each record in collection. The words position doesn’t matter.

    EX: user.Text = “andrew john jessica”;
    dictionary.Add(“andrew jackson clark”);
    dictionary.Add(“johnsson jackson clark”);
    dictionary.Add(“jessica jackson john”);

    the result will be: result[0] = 1;
    result[1] = 0;
    result[2] = 2;

    Can you help me out how to resolve that via your rule engine? I can’t figure it out!
    Maybe you can get me a piece of code like this:
    engine.For()
    .Setup(u=>u.Text)
    .MustBeOneOf(pkbWords); …. but I don’t know how to get the number of Hits (how many words were matched).

    Thank you!

    • netmatze says:

      Hello Catalin,

      I am sorry it took me so long to answer your comment but i was at vacation.

      I have read your comment but i dont know exactly why you need the rule engine for your problem.
      If you have a user and you need to check the position of a text of the user in a list of other users
      you could do the following linq statement:

      User user = new User()
      {
      Text = “andrew john jessica”
      };
      List[User] list = new List[User]();
      list.Add(new User() { Text = “andrew jackson clark” });
      list.Add(new User() { Text = “johnsson jackson clark” });
      list.Add(new User() { Text = “jessica jackson john” });
      Dictionary[User, int] itemList = new Dictionary[User, int]();
      list.ForEach(innerUser =>
      {
      var userText = user.Text.Split(‘ ‘).ToList();
      userText.ForEach(userTextPart =>
      {
      if (innerUser.Text.Split(‘ ‘).Contains(userTextPart))
      {
      var index = innerUser.Text.Split(‘ ‘).ToList().FindIndex(str => userTextPart == str);
      if(!itemList.ContainsKey(innerUser))
      itemList.Add(innerUser, index);
      }
      });
      }
      );
      foreach(var item in itemList)
      {
      Debug.WriteLine(item.Key + ” ” + item.Value.ToString());
      }

      I am not sure if that is what you need, but that is how i would solve it.

      Greetings
      netmatze

  4. Catalin says:

    The editor cuts the comment.

    EX: = user.text is -> andrew john jessica;
    dictionary[1] = andrew jackson clark etc etc;
    dictionary[2] = jojo jackson clark etc etc ;
    dictionary[3] = andrew john etc etc etc etc;

    the result will be: result[0] = 1;
    result[1] = 0;
    result[2] = 2;

  5. Matt says:

    Hi netmatze,

    Firstly thank you for this excellent piece of work and for sharing it.

    I was wondering if you could provide me some guidance on how to implement a method call on the object under test.

    For example, say Person has a method WasEmployed(DateTime date) and we would like to include that in our rule expression

    Something like: new Rule(“WasEmployed(‘2001-09-05) = true”)

    I’ve just started working with your code so haven’t had much time to formulate ideas and thought you might be able to provide some guidance before I start.

    Thanks again!

  6. pandu says:

    Hi It is very nice to use rule engine, I am using rule engine extensively in our applications. It is very handy to use. But I have one requirement, RuleValidator -> .ValidateRulesAll and ValidateExpressionRulesAll will return true or false. I am interested in which rule failed. can any please give me code snippet to get failed rule from above mentioned methods calls.

    Thanks..

    • netmatze says:

      Hello pandu,

      I implemented the method you need in the rule engine
      (RuleValidator ruleValidator = new RuleValidator();
      ruleValidator.ValidateExpressionRules(new Person[] { person1, person2 }, new Rule[] { rule1, rule2 });)
      I checked in and added the SimpleRuleLoaderPersonValidateExpressionRules Test where you can see how to use it.

      Greetings
      netmatze

  7. ravirupeliya says:

    Hi netmatze,

    First of all thanks for the amazing rule engine and extensive support. It is really very helpful.

    I have one query. What if I want to use LIKE operator like this : “Name like ‘%abc%'” ?

    Thanks in advance,
    RaviRupeliya

  8. cbh says:

    Love the idea. I am trying to set a property in an else statement like this:

    public void SimpleExpressionEvaluatorWithPlusThenMethod()
    {
    Person person1 = new Person()
    {
    Name = “Mathias”,
    Age = 36,
    Children = 2,
    Married = true,
    Birthdate = new DateTime(1976, 5, 9)
    };
    Evaluator evaluator = new Evaluator();
    var result1 = evaluator.Evaluate(” ( Name = ‘Fred’) then set Age = 13 else set Age = 11 “, person1);
    Assert.AreEqual(result1, true);
    Assert.AreEqual(person1.Age, 11);
    }

    Fails with this:
    Unable to cast object of type ‘SimpleExpressionEvaluator.AbstractSyntaxTree.SetNode’ to type ‘SimpleExpressionEvaluator.AbstractSyntaxTree.VariableNode’.

    Stack Trace:
    at SimpleExpressionEvaluator.ExpressionEvaluatorExecutor.Evaluate[T](List`1 postfixList, Dictionary`2 symbolTable, T objectValue) in c:\Users\fred\Downloads\ruleengine-43894\SimpleExpressionEvaluator\Parser\ExpressionEvaluator.cs:line 47
    at SimpleExpressionEvaluator.Evaluator.Evaluate[T](String evaluationText, T objectValue) in c:\Users\fred\Downloads\ruleengine-43894\SimpleExpressionEvaluator\Evaluator.cs:line 18

    Any Ideas? Is this construct possible in the current design?

  9. velu says:

    Hi,

    First of all Thanks for your wonderful post …

    I wanted to test the expression from string ,if fails i need to check other condition like ” else if ladder ” kind,Do we have any option to achieve the below?.Any idea/suggestions

    Actual taken from test project

    var result5 = evaluator.Evaluate(
    ” (Age > 40) then (Married = false) then SetCanReceiveBenefits(true) else SetCancelBenefits(true) else SetStopBenefits(true) “, person3);

    Looking for

    var result5 = evaluator.Evaluate(
    “(Age > 40) then (Married = false) then SetCanReceiveBenefits(true) else SetCancelBenefits(true) else (Age=25) then SetStopBenefits(true) else something(‘test’) “, person3);

    Thanks,
    Velu


Leave a reply to Catalin Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.