1

I am getting this exception:

variable 'e' of type 'MyClass' referenced from scope '', but it is not defined

I want to create dynamic rules with nested property. When I create static rules it works fine, but not in dynamic mode. Also, I want to use expression for dynamic rules using these lines of code if possible:

PatternBuilder customerPattern = builder.LeftHandSide().Pattern(typeof(Customer), "customer");
Expression<Func<Customer, bool>> customerCondition = customer => customer.Name == "John Do";
customerPattern.Condition(customerCondition);

I tried the code below to create and execute rules dynamically, but I am getting an exception. Why?

class Program
{
    static void Main(string[] args)
    {
        try
        {
            CustomRuleRepository repository = new CustomRuleRepository();

            List<RuleEngineEntity> rules = new List<RuleEngineEntity>();
            rules.Add(new RuleEngineEntity { FieldName = "Age", Name = "CustomerCheck", Value = 20 });

            repository.LoadRules(rules);

            //Compile rules
            var factory = repository.Compile();

            //Create a working session
            var session = factory.CreateSession();

            RuleEngineRequestModel ruleEngineRequestModel = new RuleEngineRequestModel { ruleList = rules, customerData = new Customer { Name = "A", Age = 24 } };

            session.Insert(ruleEngineRequestModel);

            var IspassedorNot = session.Fire();
        }
        catch (Exception e) {
            Console.WriteLine(e.Message);
        }
    }
}

public class RuleEngineRequestModel
{       
    public List<RuleEngineEntity> ruleList { get; set; }
    public Customer customerData { get; set; }
}

public class RuleEngineEntity
{
    public string Name { get; set; }
    public int Value { get; set; }
    public string Operator { get; set; }
    public string FieldName { get; set; }
}

public class Customer
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class CustomRuleRepository : IRuleRepository
{
    private readonly IRuleSet _ruleSet = new RuleSet("customerRule");

    public IEnumerable<IRuleSet> GetRuleSets()
    {
        return new[] {_ruleSet};
    }

    public void LoadRules(List<RuleEngineEntity> list)
    {
        _ruleSet.Add(
            BuildRule(list)
        );
    }

    public List<IRuleDefinition> BuildRule(List<RuleEngineEntity> list)
    {
        NRules.RuleModel.Builders.RuleBuilder builder = null;
        List<IRuleDefinition> rulesList = new List<IRuleDefinition>();
        builder = new NRules.RuleModel.Builders.RuleBuilder();
        builder.Name("CustomerDetail");
        ParameterExpression customerParameter = null;
        LambdaExpression customerCondition = null;
        PatternBuilder customerPattern = null;
        try
        {
            var orGroup = builder.LeftHandSide().Group(GroupType.Or);

            foreach (var item in list)
            {
                var andGroup = orGroup.Group(GroupType.And);

                customerPattern = andGroup.Pattern(typeof(RuleEngineRequestModel), item.Name);
                customerParameter = customerPattern.Declaration.ToParameterExpression();

                customerCondition =
                    Expression.Lambda(
                        Expression.GreaterThan(CreateParameterExpression(typeof(RuleEngineRequestModel), "customerData", typeof(Customer), item.FieldName),
                            Expression.Constant(item.Value)), customerParameter);
                customerPattern.Condition(customerCondition);
            }

            Expression<Action<IContext>> action =
                (ctx) => Console.WriteLine("Action triggered");

            builder.RightHandSide().Action(action);

            rulesList.Add(builder.Build());
        }
        catch (Exception e)
        {
        }

        return rulesList;
    }

    public Expression CreateParameterExpression(Type type, string propertyName, Type type2, string propertyName2)
    {
        ParameterExpression pe = Expression.Parameter(type, "e");
        Expression left = Expression.Property(pe, type.GetProperty(propertyName));
        return Expression.Property(left, type2.GetProperty(propertyName2));
    }
}

1 Answers1

2

You have a CreateParameterExpression method, that's supposed to create a property access expression. But in that method you are creating a parameter expression there with a name "e", which does not correspond to any pattern that you defined in your rule. This causes the "variable not found" exception.

You simply need one MemberExpression to access the customerData property, and then another one to access the nested property, configured in the RuleEngineEntity. Here is an updated BuildRule method that implements the necessary expression tree.

public List<IRuleDefinition> BuildRule(List<RuleEngineEntity> list)
{
    var builder = new NRules.RuleModel.Builders.RuleBuilder();
    builder.Name("CustomerDetail");

    var orGroup = builder.LeftHandSide().Group(GroupType.Or);

    foreach (var item in list)
    {
        var andGroup = orGroup.Group(GroupType.And);

        var modelPattern = andGroup.Pattern(typeof(RuleEngineRequestModel), item.Name);
        var modelParameter = modelPattern.Declaration.ToParameterExpression();
        var customerData = Expression.Property(modelParameter, nameof(RuleEngineRequestModel.customerData));

        var customerCondition = Expression.Lambda(
            Expression.GreaterThan(
                    Expression.Property(customerData, item.FieldName),
                    Expression.Constant(item.Value)),
                modelParameter);
        modelPattern.Condition(customerCondition);
    }

    Expression<Action<IContext>> action =
        ctx => Console.WriteLine("Action triggered");

    builder.RightHandSide().Action(action);

    var rule = builder.Build();
    return new List<IRuleDefinition> {rule};
}
Sergiy Nikolayev
  • 702
  • 4
  • 13
  • Thanks for reply but this will work if i have to access property from incoming Model, I tried your way earlier but my case is to access nested property so in this case it fails.Check my model and property i m accessing. – Ghanshyam Singh Feb 13 '19 at 04:07
  • 1
    @GhanshyamSingh - ok, got it. I missed the customer was nested inside the model. The names of variables were misleading. I updated the answer with the reworked version of the BuildRule method. – Sergiy Nikolayev Feb 13 '19 at 05:21
  • Thank you so much for your help ^_^ – Ghanshyam Singh Feb 13 '19 at 08:37
  • One more question what if i change customerData as List, The model like below – Ghanshyam Singh Feb 13 '19 at 08:39
  • public class RuleEngineRequestModel { public List ruleList { get; set; } public List customerData { get; set; } } – Ghanshyam Singh Feb 13 '19 at 08:40
  • also unable to bind current rule with action. – Ghanshyam Singh Feb 13 '19 at 09:06
  • If you change the container to a List, you would need to create an expression tree that gets the customer from it, though it's unclear to me which customer - first or all of them.. And this mostly just becomes a general question about how to construct expression trees in C# to access values in a collection. There are plenty of answers on SO already, so I suggest you search and see if it's already answered. For example: https://stackoverflow.com/questions/31924907/accessing-elements-of-types-with-indexers-using-expression-trees – Sergiy Nikolayev Feb 14 '19 at 04:42
  • Regarding "binding current rule with action" - not sure what you mean. If I compile and run your example with my changes on top, the rule fires and the action gets executed and prints "Action triggered" – Sergiy Nikolayev Feb 14 '19 at 04:43
  • Ok i'll look into this url provided by you for nested property with array list, again thanks for your help. – Ghanshyam Singh Feb 26 '19 at 14:33
  • Not able to use Expression.ArrayIndex() how to make this indexer value dynamic so it checks all data in customerData var dataList = Expression.ArrayIndex(customerData, Expression.Constant(0)); var expres=Expression.Property(customerData, "Age"); – Ghanshyam Singh Aug 29 '19 at 09:40