Recalculating dependant properties in business rules

Recalculating dependant properties in business rules

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


ianmcd0 posted on Thursday, December 22, 2011

I came across the post at http://forums.lhotka.net/forums/p/10941/50937.aspx

I have a similar requirement to what is discussed in this post and have tried implementing two rules based on the example that JonnyBee provided.  Having problems though...

My situation is a little different.  I have a product class with three properties CostPrice, SellingPrice and Margin.  All three properties are read/write.

If I change the SellingPrice OR the cost price I want to update the Margin. 

If I change the Margin I want to update the Selling Price

So I have created two rules.  CalcMargin accepts margin as the primary property as well as costprice and sellingprice.  CalcSellingPrice accepts sellingprice as the primary property as well as costprice and margin.

For my situation I don't want the rule to be executed when the primary property is changed. For example when the user changes the margin I should only be executing CalcSellingPrice, NOT CalcMargin.  Currently both rules are being executed.

Can I make use of BusinessRules to implement this behaviour?  (I know I can fall back to doing this in the property sets but I like the idea of defining a rule that might be useful in the future).

Thanks.

JonnyBee replied on Thursday, December 22, 2011

Hi,

You'll need to rethink a little on how you attach the rules:

CalcSellingPrice should have PrimaryProperty = MarginProperty with SellingPrice as AffectedProperty and CostPrice as InputProperty.
CalcMargin should be attached to both SellingPriceProperty and CostPriceProperty (as PrimaryProperty) with Margin as AffectedProperty and al input values as InputProperties.

And both rules should only execute when the PrimaryProperty is changed by user.

So the CalcMargin rule should look like this:

    private class CalcMargin : Csla.Rules.PropertyRule
    {
      private readonly IPropertyInfo _costPriceProperty;
      private readonly IPropertyInfo _sellingPriceProperty;
      private readonly IPropertyInfo _marginProperty;
 
      public CalcMargin(IPropertyInfo primaryProperty, IPropertyInfo costPriceProperty, IPropertyInfo sellingPriceProperty, IPropertyInfo marginProperty)
        : base(primaryProperty)
      {
        _costPriceProperty = costPriceProperty;
        _sellingPriceProperty = sellingPriceProperty;
        _marginProperty = marginProperty;
 
        InputProperties = new List<IPropertyInfo>();
        InputProperties.Add(_costPriceProperty);
        InputProperties.Add(_sellingPriceProperty);
        AffectedProperties.Add(_marginProperty);
 
        CanRunAsAffectedProperty = false;
        CanRunInCheckRules = false;
        CanRunOnServer = false;
      }
 
      protected override void Execute(RuleContext context)
      {
        var cost = (decimal) context.InputPropertyValues[_costPriceProperty];
        var selling = (decimal) context.InputPropertyValues[_sellingPriceProperty];
 
        // calc margin
        var margin = .....
 
        context.AddOutValue(_marginProperty, margin);
      }
    }

Usage:
      BusinessRules.AddRule(new CalcMargin(CostPriceProperty, CostPriceProperty, SellingPriceProperty, MarginProperty));       BusinessRules.AddRule(new CalcMargin(SellingPriceProperty, CostPriceProperty, SellingPriceProperty, MarginProperty));

which will recalculate the margin when the user changes either CostPrice or SellingPrice

ianmcd0 replied on Friday, December 23, 2011

Jonny,  thank you, the example you provided is working well.  There is one remaining nuance that I am unsure how to deal with.

I am assigning my two rules as below:

  BusinessRules.AddRule(new CalcMargin(CostPriceProperty, CostPriceProperty, SellingPriceProperty, MarginProperty));

  BusinessRules.AddRule(new CalcMargin(SellingPriceProperty, CostPriceProperty, SellingPriceProperty, MarginProperty));

  BusinessRules.AddRule(new CalcSellingPrice(MarginProperty, CostPriceProperty, MarginProperty, SellingPriceProperty));

When the user changes the selling price the CalcMargin rule executes.  Unfortunately CalcSellingPrice then also fires.  When I calculate my margin there is some rounding required so I can't allow the selling price to be calculated.  For example the user might enter a value of 5 and it gets changed to 5.01 when they leave the field.

Reading the post at http://www.lhotka.net/weblog/CSLA4BusinessRulesSubsystem.aspx, Rocky explains that AddOutValue causes updates using LoadProperty so I would have hoped that CalcSellingPrice wouldn't fire.  I'm not sure how to fix this...

JonnyBee replied on Friday, December 23, 2011

Make sure that CalcSellingPrice inherits from PropertyRule and set

        CanRunAsAffectedProperty = false;
        CanRunInCheckRules = false;
        CanRunOnServer = false;

This will make sure that CalcSellingPrice is not run as Affected property from another rule or in CheckRules.

AddOutValue in itself will only update the value (and raise OnPropertyChanged) but the property must also be in AffectProperties list and this causes the rules for those properties to be rechecked.

ianmcd0 replied on Wednesday, December 28, 2011

Embarrassed  My cut and paste skills are failing me - I had been inheriting from CommonBusinessRule and failed to set the properties correctly as you show above.  All is perfect now .  Really grateful for your help.  Hope you had a good Xmas and all the best for 2012!

Copyright (c) Marimer LLC