Business rules - adding InputProperties causes rules to run

Business rules - adding InputProperties causes rules to run

Old forum URL: forums.lhotka.net/forums/t/11424.aspx


j0552 posted on Friday, June 22, 2012

CSLA version: 4.3.10.0

 

Hi

 

If I create a rule for a property with additional property added to InputProperties in the constructor, when the second property's value changes the rule for the first property executes. The example below demostrates this behaviour.

 

    [TestClass]

    public class DummyTest

    {

        [TestMethod]

        public void DummyRuleTest()

        {

            var obj = Dummy.NewDummy();

            obj.Bar = 999;

        }

    }

   

    [Serializable]

    public class Dummy : BusinessBase<Dummy>

    {

        private Dummy()

        {

        }

 

        public static Dummy NewDummy()

        {

            return DataPortal.Create<Dummy>();

        }

 

        public static readonly PropertyInfo<int> FooProperty = RegisterProperty<int>(c => c.Foo);

        public int Foo

        {

            get { return GetProperty(FooProperty); }

            set { SetProperty(FooProperty, value); }

        }

 

        public static readonly PropertyInfo<int> BarProperty = RegisterProperty<int>(c => c.Bar);

        public int Bar

        {

            get { return GetProperty(BarProperty); }

            set { SetProperty(BarProperty, value); }

        }

 

        protected override void AddBusinessRules()

        {

            base.AddBusinessRules();

 

            BusinessRules.AddRule(new FooRule(FooProperty, BarProperty));

        }

 

        #region Nested type: FooRule

 

        private class FooRule : BusinessRule

        {

            public FooRule(IPropertyInfo primaryProperty, IPropertyInfo barProperty)

                : base(primaryProperty)

            {

                // adding barProperty to FooRule InputProperties causes the rule to run when Bar's value is changed

                InputProperties = new List<IPropertyInfo> { primaryProperty, barProperty };

 

                //InputProperties = new List<IPropertyInfo> { primaryProperty };

            }

 

            protected override void Execute(RuleContext context)

            {

                // Changing the value of Bar causes Foo's rule to execute.

            }

        }

 

        #endregion

 

        protected override void DataPortal_Create()

        {

        }

    }

 

This doesn't seem to be expected behaviour and in fact in the real world causes rules to run many more times than is necessary. Is this a bug or can you explain the principle of the described behaviour.

 

Thanks

Andrew 

JonnyBee replied on Friday, June 22, 2012

This is intentional - as you use InputProperties to send additional field (typically for comparison) to the Execute method and want to "revalidate" the field whan any of the input properties change.

Take f.ex these rules:

BusinessRules.AddRule(new GreaterThanOrEqual(Num2Property, Num1Property));
BusinessRules.AddRule(
new LessThan
(StartDateProperty, EndDateProperty));
BusinessRules.AddRule(
new GreaterThan
(EndDateProperty, StartDateProperty));
BusinessRules.AddRule(new NotifyChangedWhenInputChanged
(CalculatedProperty, Param1Property, Param2Property));
BusinessRules.AddRule(new CalcSum(SumProperty, Num1Property, Num2Property, Num3Property));

So:
When Num1 is changed rerun rule for Num2
When EndDate is changed - rerun rule for StartDate.
When Num1 or Num2 or Num3 is changed rerun rule for Sum

The alternative (as in 3.x and pre 4.1) would be to add a number of Dependency rules.

From my experience this typically is the desired behavior.

AND - You can avoid this if you don't want the secondary rue to run:

    private class FooRule : PropertyRule
    {
        public FooRule(IPropertyInfo primaryProperty, IPropertyInfo barProperty) : base(primaryProperty)
        {
            InputProperties = new List<IPropertyInfo> { primaryProperty, barProperty };
            // Tell RuleEngine to NOT run this rule when input property changes or as Dependency/Affected Property
            CanRunAsAffectedProperty = false;
        }
 
        protected override void Execute(RuleContext context)
        {
 
            // Changing the value of Bar will NOT causes Foo's rule to execute.
        }
    }

j0552 replied on Friday, June 22, 2012

OK, now I think I get!

 

I think the main problem I've been having is misunderstanding the purpose of  AffectedProperties and implemented rules around this misunderstanding. With your explanation I've been able to see how I can simplify my rules and avoid executing rules unnecessarily.

 

Is it right that we only need to use AffectedProperties when adding a context.Add... to an extra property?

 

Thanks to both of you for your detailed explanations.

 

Andrew

JonnyBee replied on Friday, June 22, 2012

Yes, you need to add other properties to AffectedProperties in order to use context.Add.... to other properties than PrimaryProperty.

Another subtle difference is that InputProperties as Dependency is ALWAYS considered a declarative dependency and always enforced whereas AffectedProperties is aggregated as the rules is executed.

Ex: If you have several rules at different Priority level you would have to add Dependency rules with Priority < 0 in order to be sure the dependency would be  enforced. So if you got a broken rule at level 0 the rules with Priority > 0 will not be executed and thus their AffectedProperties will have NO impact!!

JonStonecash replied on Friday, June 22, 2012

This is a featuire not a bug.  Big Smile In the simple case, the validity of the rule is determined by a single property.  For example, a telephone number must have so many numeric digits; any other condition causes the rule to fail.  But there are situations where several values must be inspected to establish the validity of the property.  The typical example here is a date range in which the start date must be specified and that the end date, if specified, must be greater than or equal the current date and greater than or equal to the start date.  I would code this as two separate rules:

  1. StartDate must be specified.  This rule only needs to inspect the value of the start date.
  2. EndDate, if specified, must be greater than or equal to start date.  This rule needs to inspect the values of the start date and the end date.  A change in either value means that the rule must be re-evaluated.

Any change to the start date causes rule #1 to be re-evaluated and causes rule #2 to be re-evaluated, because rule #2 depends on the start date.  Any change to the end date only causes rule #2 to be re-evaluated.  However, note that running rule #2, may make the start date invalid; we cannot know which property is wrong, just that the combination is wrong.

It is not possible to tell from your example code whether the rule actually needs the value of "bar" to determine validity.  If it does, the inclusion of "bar" in the input values is exactly what you want to do.  If the rule does not need "bar" then you should remove that from the input values. 

There are ways to change the values of properties without triggering the evaluation of rules.  You can "load" properties (which does not check rules) rather than using "set" (which does check the rules) to change the values.  If you are making a lot of changes at the same time (such as might be the case when loading the data from a data source), you can use the BypassPropertyChecks mechanism.  Just make sure that you run the "check rules" method to ensure that the state of the business object matches its contents.

Jon Stonecash

 

Copyright (c) Marimer LLC