microsoft / RulesEngine

A Json based Rules Engine with extensive Dynamic expression support
https://microsoft.github.io/RulesEngine/
MIT License
3.6k stars 543 forks source link

Incorrect result with LocalParam #517

Closed lichengxihuang closed 1 year ago

lichengxihuang commented 1 year ago

Here is my code snippet, and I am using the LocalParam. The answer seems incorrect.

public class MyObject
{
    public string Name { get; set; }

    public int Count { get; set; }
}
public void Run()
{
    List<Workflow> workflows = new List<Workflow>();
    Workflow workflow = new Workflow();
    workflow.WorkflowName = "Test Workflow Rule";

    List<Rule> rules = new List<Rule>();

    Rule rule = new Rule
    {
        RuleName = "Test Rule",
        LocalParams = new List<LocalParam>
        {
            new LocalParam
            {
                Name = "threshold",
                Expression = "3"
            },
            new LocalParam
            {
                Name = "myList",
                Expression = "new int[]{ 1, 2, 3, 4, 5 }"
            }
        },
        SuccessEvent = "Count is within tolerance.",
        ErrorMessage = "Not as expected.",
        Expression = "myList.Where(x => x < threshold).Contains(myObj.Count)",
        RuleExpressionType = RuleExpressionType.LambdaExpression
    };

    rules.Add(rule);

    workflow.Rules = rules;

    workflows.Add(workflow);

    var reSettingsWithCustomTypes = new ReSettings { CustomTypes = new Type[] { typeof(Utils) } };
    var bre = new RulesEngine.RulesEngine(workflows.ToArray(), reSettingsWithCustomTypes);

    var myObject = new MyObject()
    {
        Name = "My Object",
        Count = 2
    };

    var rp1 = new RuleParameter("myObj", myObject);

    List<RuleResultTree> resultList = bre.ExecuteAllRulesAsync(workflow.WorkflowName, rp1).Result;
    PrintResult(resultList); // True

    myObject.Count = 3;

    resultList = bre.ExecuteAllRulesAsync(workflow.WorkflowName, rp1).Result;
    PrintResult(resultList); // True, **this is incorrect**.
}

This gives results both true, which is incorrect, as the second result should be false.

Instead, if I replace the localParam with the constant number 3, the results returned are correct: True, False.

Rule rule = new Rule
{
    RuleName = "Test Rule",
    LocalParams = new List<LocalParam>
    {
        new LocalParam
        {
            Name = "myList",
            Expression = "new int[]{ 1, 2, 3, 4, 5 }"
        }
    },
    SuccessEvent = "Count is within tolerance.",
    ErrorMessage = "Not as expected.",
    Expression = "myList.Where(x => x < 3).Contains(myObj.Count)",
    RuleExpressionType = RuleExpressionType.LambdaExpression
};
abbasc52 commented 1 year ago

@lichengxihuang can you share which OS and dotnet version you are using?

lichengxihuang commented 1 year ago

@lichengxihuang can you share which OS and dotnet version you are using? I am using Windows 11 and .net 7.0.

lichengxihuang commented 1 year ago

Are there ways to use List, Set, Dictionary as the LocalParam?

If I change the above code to the following

Rule rule = new Rule
{
    RuleName = "Test Rule",
    LocalParams = new List<LocalParam>
    {
        new LocalParam
        {
            Name = "myList",
            Expression = "new List<int> { 1, 2, 3, 4, 5 }"
        }
    },
    SuccessEvent = "Count is within tolerance.",
    ErrorMessage = "Not as expected.",
    Expression = "myList.Where(x => x < 3).Contains(myObj.Count)",
    RuleExpressionType = RuleExpressionType.LambdaExpression
};

It returns error

Error while compiling rule Test Rule: Type 'List' not found, in ScopedParam: myList

abbasc52 commented 1 year ago

@lichengxihuang the original issue is fixed in v5.0.2

Are there ways to use List, Set, Dictionary as the LocalParam?

Well creating List using new might not work as generics are not supported in expression. You can however create an array and do .ToList() . I havent tried similar approach for Set or Dictionary but it may work till some extent.

If not there is always a workaround to write a custom Utils class and generate it for you. In your example this should work:

new int[] { 1, 2, 3, 4, 5 }.ToList()