Business Rule Context Information

Business Rule Context Information

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


rxelizondo posted on Friday, April 18, 2014

Hello

We have a class that contains property X with some expensive rules associated to it. We want all rules to fire if the value of property X gets changed, but we would like to suppress some of its rule if the rules are being fired due to an explicit call to ‘CheckRules(X)’ or due to property X having dependencies with other properties.


In other words, we need context to know why are the businesses rules being fired so we can determine whether to run them or not.


I took a quick look at the business rule runtime information and couldn’t find anything that could give me that context. Am I missing something or is it not possible to do what we want to do.


By the way, we already have a work around that solves our issue but I was wondering if I am missing something and there is a formal way of doing what we are doing the proper way.


We are currently using CSLA 4.2.2.0


Thanks.

ajj3085 replied on Saturday, April 19, 2014

I think the normal way to handle this would be to make the expensive rules async, and / or setting their priorities.  If a rule fails, rules at a higher priority aren't run as long as the priority is greater than 1.

You may also only choose to run certain rules on the server.

JonnyBee replied on Sunday, April 20, 2014

Hi,

From my blogpost: http://jonnybekkum.wordpress.com/2011/08/29/csla-4-2-rules-update/

4. Rules can now specify conditions for when the rule is not allowed to run.

Typical usage is for any rule that you will only allow to run when the user edits a value and f.ex do an async lookup to a database.
By default and to not brake any existing code, rules will run without restrictions.

Flag Property rule Object rule
CanRunInCheckRules         Yes           Yes
CanRunAsAffectedProperty         Yes           No
CanRunOnServer         Yes           No

Object level rules is executed when either <bo>.CheckObjectRules() or <bo>.CheckRules() is called.

PropertyRules is executed when either <bo>.PropertyHasChanged(property) or <bo>.CheckRules() is called.
And PropertyHasChanged is implicitly called when SetProperty changes a value so most developers will never call PropertyHasChanged.

CanRunInCheckRules when false means that the rule will not be executed when <bo>.CheckRules() is called.

CanRunAsAffectedProperty when false means that this property rule will not run when this property is an affected property on another property rule.

CanRunOnServer when false means that this property rule will not run on the “logical” server side, by all practical means in Data Access Layer.
So when CheckRules is called from Data Access do not execute this rule when set to false.

So to rephrase the answer:

Your rules can declaratively tell the rule engine to NOT run the rule under certain conditions.
Your implementation does NOT have to add extra checks.

rxelizondo replied on Sunday, April 20, 2014

Thanks guys.

 

Jonny:

I did look into the 'RunMode' flag but that did't  help us. The problem is that we are doing a 'CheckRules(PropertyX)' call, we are not doing a 'CheckRules()' call.

What we need is to be able to distinguish between a call to 'CheckRules(PropertyX)' done by the CSLA as a consequence of the actual property value being changed and a call to 'CheckRules(PropertyX)' that was not a consequence of the actual property value being changed. With this information at hand, we could determine whether to execute the rule or not.

The reason such feature would be nice is because there are times when there is absolutely no need to run some rules if the property value was not changed. Take the 'Required' rule as an example, this rule clearly needs to execute every time the property value changes but it makes no sense to execute the rule if the value of the property was not changed. Of course, the 'Required' rule is a trivial rule, but imagine a rule that takes a while to execute. 

Thanks.

ajj3085 replied on Sunday, April 20, 2014

Hey Rene, I believe by default Csla tracks if they property value actually does change and won't run rules if it didn't.  so if the value is empty string and you set the property to empty string Csla knows its not different and so won't run any rules.   What are you doing that you are calling CheckRules yourself? 

JonnyBee replied on Sunday, April 20, 2014

Well, thats exactly the same as Csla.Core.BusinessBase base class calls:

protected virtual void PropertyHasChanged(Csla.Core.IPropertyInfo property)
{
  MarkDirty(true);
  var propertyNames = BusinessRules.CheckRules(property);
  if (ApplicationContext.PropertyChangedMode == ApplicationContext.PropertyChangedModes.Windows)
    OnPropertyChanged(property);
  else
    foreach (var name in propertyNames)
      OnPropertyChanged(name);
}

So - the primary responsibility if CheckRules(property) is to rerun all rules when this property has been changed by a user in the UI.

The big challenge is how the rule engine updates the broken rules collection!!

  1. When rules is completed for a property
  2. Clear all broken rules that originate from this property
  3. Then add broken rules from the last run.

So if you omit rules from running the second/third/fourth time when the rule is is called as a "affected" property - then the broken rule information is LOST!

So - if I assume that your code does not manually call CheckRules(property) - the PropertyRule and CommonBusinessRule base class offers the 3 flags from my blog post and allows you to instruct the rule engine to not run a certain rule when called as "affected property".

So the CSLA goal is that your code should not need to call CheckRules(property)!

rxelizondo replied on Monday, April 21, 2014

 

Guys, thank you very much for your help.

Ok, I guess it is time for the obligatory example that illustrate the issue we are running into Smile

Imagine you have a Recipe class with a collection of recipe Ingredients (your typical parent child relationship). Imagine also that the Recipe class has a property called IsHealthy, when the IsHealthy property is set to true it is expected that all recipe Ingredients are considered to be healthy ingredients.

So you create a recipe named Snackaroo and add Broccoli, Carrots and Bacon as the ingredients. So far, the Recipe is valid but then you decide to set Recipe IsHealthy flag to true. At that point, you would need to iterate through all the recipe Ingredients and trigger their appropriate rules on their appropriate properties. Once the rules on the children have been executed, I would expect the Ingredient representing Bacon to trigger a broken rules saying something like "Bacon is not a healthy ingredient" causing the Recipe to be invalid.

Now, I may be wrong about this, but the way I know to go about checking rules on child objects after a change is originated on a parent object is to trap the change on the parent and iterate through all the child objects calling their ‘CheckRules(SomeProperty)’ method so that the rules on that property can execute. This is necessary because there are no built in dependencies between parent and child properties in the CSLA (as far as I know).

Am I missing the obvious?

Thanks.

ajj3085 replied on Monday, April 21, 2014

I think that rule actually belongs on the parent, not the ingredients.  The ingredient itself is not invalid, its the fact that its included on a healthy recipe which is the problem.  The same ingredient would be fine on a recipe which is not marked as healthy, right?

rxelizondo replied on Monday, April 21, 2014

 

Andy,

Maybe you are correct, but if you ask around, 50% of the people will tell you that you are right and 50% will tell you you are wrong and the debate would go on for an eternity. To me, both approaches are valid, it really depends on your paradigm I guess.

At the end of the day, it really does not matter where you add the rule because you will still run into the same issue. For example, if you implement the rule on the parent Recipe, every-time you add new Ingredient or change and existing one the Recipe class will needs to intercept the change and then do a CheckRules(IsHealthyProperty) to update the rules on the IsHealthy property so we are back to square one. This is because the CSLA does not support dependencies on properties from different classes.

Is that no the case?

 

JonnyBee replied on Monday, April 21, 2014

My initial answer is the same as Andy - this is the reponsibility of the Recipe object. 

You could however also consider to add this rule on the child objects as an "object level" rule.

Object level rules are only executed from BusinessRules.CheckRules and BusinessRules.CheckObjectRules and you can even mark an object rule with CanRunInCheckRules= false so that the rule will only ever be called from your code calling BusinessRules.CheckObjectRules.

rxelizondo replied on Monday, April 21, 2014

Jonny,

Lets suppose that we change the rule so that it now resides on the Recipe object. We will still have the exact same issue right? Every time a new Ingredient is added or an existing one is changed the Recipe class will need to intercept the change on the child objects and then do a  'CheckRules(IsHealthyProperty)' on itself to update the rules on its IsHealthy property right? So we still have the exact same problem of running unnecessary rules.

If you ask me, as mentioned before, the real solution is for the CSLA BusinessRule to be able to differentiate between a call to 'CheckRules(SomeProperty)' due to the property value actually changing or due to an explicit call to 'CheckRules(SomeProperty)' that was not due to the property value changing. For example, the BusinessRule class could expose a 'PropertyValueHasChanged' flag that would help us tell if the rule is being executed because the property value was changed or not. The BusinessRule could also cache the previous rule result and expose a 'PreviousRuleResult' property that we could use to recreate the rule in a very efficient manner.

So the code could look something like this:

 

protected override void Execute(RuleContext context)

{

   if ( context.PropertyValueHasChanged )

      // Property value was changed so run full logic.

   else

      // Property value did not change, no need to run full rule logic, used the previous cached rule result because we know the rule result will be the same anyway.

       context.AddResult(context.PreviousRuleResult);

}

This could all be done by the CSLA automatically via a setting such as 'ReusePreviousRuleResultIfPropertyDidNotChanged' (or whatever). If the value of that flag is true then the CSLA could automatically use the cached result to recreate the rules in milliseconds.

Maybe the suggestion is totally flawed but I am just throwing it out there.

Thanks.

 

JonnyBee replied on Tuesday, April 22, 2014

Rene Elizondo

hanging. For example, the BusinessRule class could expose a 'PropertyValueHasChanged' flag that would help us tell if the rule is being executed because the property value was changed or not. The BusinessRule could also cache the previous rule result and expose a 'PreviousRuleResult' property that we could use to recreate the rule in a very efficient manner.

NOOOOO -- CSLA only supports TYPE-rules. 

 IE: The single instance of the rule is used for ALL instances of the given type. You should NOT - NOT -  cache instance results in a business rule!!!!
I always view any business rule as a "static instance" - and it can NOT cache results.

rxelizondo replied on Tuesday, April 22, 2014

 

Yes, but the 'PropertyValueHasChanged' and 'PreviousRuleResult' values come as part of the rule context and not the rule itself. Although these properties can be exposed by the rule class itself, behind the scenes, they get their value from the rule context.

If you don't want to dirty up the BusinessRule class like that, then exposing these values just on the rule context will work just as well but then the CSLA would not be able to handle things automatically.

Without giving this too much tough, I don't see an immediate problem with an optimization feature like this but I am obviously not a master of the CSLA so i may be way off base here.

Just a suggestion.

Thanks.

JonnyBee replied on Tuesday, April 22, 2014

One key issue is that RuleContext object does NOT survive between runs and there is no safe place to get the "PreviousRuleResult". 

F.ex  if you have 3 rules defined for a single property - then each rule will get a separate RuleContext (and may specify separate InputProperties/AffectedProperties/OutputProperties). You may even have a rule in the middle that shortcircuit execution and the last rule will not be called at all. How do you handle "PreviousRuleResult" in these circumstances? 

Another issue is that the Rule instance is a shared instance for ALL objects of this type. Csla 4.x has NO instance rules like you had in Csla 3.x

So to summarize:

rxelizondo replied on Tuesday, April 29, 2014

Jonny, 

Thank you very much for taking the time to respond to my question. Sorry for taking so long to reply, something came up and I temporarily lost track of this post.

Well, I am afraid that I am in over my head here, without a good understanding of the CSLA inner workings I feel I may be wasting your time but if you feel this post has some merit then here is my reply to the issues you brought up above.

I think the difference between the way you are approaching this and the way I am approaching this is that you are focusing on the BusinessRule and I am focusing on the BusinessObject. The way I see it, every rule context has a business object attached to it that is accessible through the Target property. So if the rule needs to get previously rule information it can use the Target business object to achieve this.

Here is how I see the sequence of events playing out.

  1. PropertyX rules are executed due to a 'CheckRules(PropertyX)' method call.
  2. Rule engine makes a copy of the current PropertyX rule results FROM the business object instance TO the business object instance. The key here is that the copy of the rule results is stored in the business object itself and not the actual static rule or rule context.
  3. Rule engine clears the current PropertyX rule results so it can create new rule results. This is not big deal since we already made a copy of the previous rule results.
  4. Rule engine begins calling the PropertyX rules.
  5. Rule engine detects that a certain rule has the "DontRunUnlessPropertyValueActuallyChanges" flag set to true.
  6. Rule engine realizes that the rule is being executed due to a 'CheckRules(PropertyX)' method call which means the property value did not change.  The rule engine also realizes that because the "DontRunUnlessPropertyValueActuallyChanges" flag set to true, the rule result would be identical if the rule was executed again so instead of re-executing the rule, it reaches out to the target business object vie the rule context Target property and extracts the previous rule result from the rule results that were copied before the rules started executing.
  7. Rule engine recreates the rule result without actually executing the rule (nice optimization enhancement).
  8. Rule engine is done running rules so it clears the copied rule results.

So as far as I can see, accessing previous rule results to recreate rule results is totally doable.

Is this not the case?

Thank you.

JonnyBee replied on Wednesday, April 30, 2014

No, this is not the case. It would be a total bloat of the BO to add all this information that would only be used by a few rules.

Other problems include

  1. All rules may not be executed in a run (and you would end up with a "non-consistent" last result).
    Look at f.ex  "ShortCircuiting" rules, "StopProcessing" flag on context.AddXYZResult and the  "ProcessThroughPriority" setting on BusinessRules
  2. Async rules by default do NOT get the Target object (this property is default null)
  3. How should the BO behave after a CancelEdit (N-Level undo) - which previous result should be made available? 
  4. Should previous result also be passed between Tiers (from Server to Client)? 
  5. The result of your rule may also have other "InputProperties" that affects the outcome.
    Only looking at the primary property could lead to unexpected results.

Your BusinessObject could implement a "previous" ruleset and the BusinessRule _could_ read values from it - but use at your own risk.
I will NOT propose to add this behavior to CSLA  framework and RuleEngine

rxelizondo replied on Wednesday, April 30, 2014

 

Honestly, I still don't see the issue here. As far as I can see, all the concerns you listed above are either not applicable or easily addressed.

Nevertheless, I trust your judgment because your understanding of the CSLA goes far beyond mine and there is a big chance I may be missing the obvious.

That said, I appreciate you taking the time entertaining my proposal. I have a good day!

Thanks,

JonnyBee replied on Sunday, April 20, 2014

My general recommendation is:

  1. All rules that perform validation (and sets Error/Warn/Info message) must ALWAYS run to keep consistency in validation messages (or else validation messages that are not set when called as a "affected" property will simply be removed and no longer shown to the user!!).
  2. Async rules should run after all validation rules with a higher priority to prevent them from running if field does not pass validation (Error)
  3. Avoid having more than one async rule on a field (you'll never know which one is finished first).

By default BusinessRules is run as follows when a property is edited by a user:

  1. Call CheckRulesForPropertymethod with parameters Property and Cascade=true
    1. Clear all BrukenRules from the Property
    1. Get list of BusinessRules for Property
    2. Call RunRuleswith parameters RulesForProperty and Cascade=true to run rules for this property.
      1. ForEach rule in RulesForProperty
        1. Call Rule.Execute method
      2. Update property values from context.OuputProperties collection
        1. if value was changed add Property to DirtyProperties
      3. Return RuleResult -  a list of AffectedProperties and lst of DirtyProperties
    3. If  (cascade)
    1.  
      1. For the aggreate of (context.AffectedProperties from the rules checked) and
        (properties that have BusinessRules with PrimaryProperty as InputProperty)
        take distinct PropertyInfo
        1. call CheckRulesForProperty with parameters Property and Cascade=false.

But then the rule engine stops is rechecking. So the rule engine will only cascade down to the next level and then stop rechecking rules.
(This is default behavior for CSLA 4.x)

Copyright (c) Marimer LLC