Who should execute Child Object Business Rules

Who should execute Child Object Business Rules

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


MarcBoissonneault posted on Friday, June 01, 2012

Hi,

I have an object "Bar" that is a root object.
I've added a Business rule on this object (Property rule).
The rule is really CPU intensive and need to be run only when the user asks to validate the object so I've implemented the property like this:

public DateTime Start
{
       get { return GetProperty(StartProperty); }
       set
       {
                ((ICheckRules)this).SuppressRuleChecking();
                SetProperty(StartProperty, value.Date);
                ((ICheckRules)this).ResumeRuleChecking();
        }

With this mechanism in place, the rule is not called every time the property changed.
So calling BusinessRules.GetAllBrokenRules(this, true) return an empty list of BrokenRules.

Now if I want to validate this object I need to manually call ((ICheckRules)this).CheckRules().
That is fine!

 

My problem come when I want to use The "Bar" object as an item (Child) of a Collection that is a child of another object.
For example if I have a "Foo" object that contains a BusinessList of "Bar" objects.
Calling  BusinessRules.GetAllBrokenRules(fooObject, true) with an invalid Bar item on a "Foo" object BEFORE running ((ICheckRules)subBarObject).CheckRules() on all BarObject creates an empty list of BrokenRules.

To correct this behavior I need to Call the "CheckRules" method on all children.
After these call are made, the call to BusinessRules.GetAllBrokenRules(fooObject, true) return the errors contained in the children objects.

 

My interrogation is that I'm not sure if:
On my side I should override the ICheckRules.CheckRule method on all root objects to call "CheckRules" on the items of child list
OR
Csla.BusinessBase class should do it for me.
Especially if I have registered the BusinessList property as Child (RelationshipTypes.Child)?

 

To understand what’s happening take a look at the attached project.
Start the app and follow the steps.

 

Thanks a lot

JonnyBee replied on Saturday, June 02, 2012

MarcBoissonneault
public DateTime Start
{
       get { return GetProperty(StartProperty); }
       set
       {
                ((ICheckRules)this).SuppressRuleChecking();
                SetProperty(StartProperty, value.Date);
                ((ICheckRules)this).ResumeRuleChecking();
        }
}

This is the same as:

public DateTime Start
{
       get { return GetProperty(StartProperty); }
       set { LoadProperty(StartProperty, value.Date); }
}

My preference is to make "expensive rule" into async rules.

The primary goal of Business Rules is to

So the expensive rule should

Imagine a list of N Bar items and you execute all rules for each bar - no matter if the user has made an edit to one, two or all items? That would really slow down your app, wouldn't it?

So I would consider to add an CPUIntensiveCommand with async dataportal call:

  [Serializable]
  public class CPUIntensiveCommand : CommandBase<CPUIntensiveCommand>
  {
 
    //public static readonly PropertyInfo<string> Param1Property = RegisterProperty<string>(c => c.Param1);
    //public string Param1
    //{
    //  get { return ReadProperty(Param1Property); }
    //  private set { LoadProperty(Param1Property, value); }
    //}
 
    public static readonly PropertyInfo<int> ResultProperty = RegisterProperty<int>(c => c.Result);
    public int Result
    {
      get { return ReadProperty(ResultProperty); }
      private set { LoadProperty(ResultProperty, value); }
    }
 
    #region Factory Methods
 
    public static void BeginExecute(string param1, object userState, EventHandler<DataPortalResult<CPUIntensiveCommand>> callback)
    {
      var cmd = new CPUIntensiveCommand();
      // cmd.Param = param1;
      DataPortal.BeginExecute<CPUIntensiveCommand>(cmd, callback, userState);
    }
 
    public static CPUIntensiveCommand Execute(string param1)
    {
      CPUIntensiveCommand cmd = new CPUIntensiveCommand();
      // cmd.Param = param1;
      return DataPortal.Execute<CPUIntensiveCommand>(cmd);
    }
 
    public CPUIntensiveCommand()
    { /* require use of factory methods */ }
 
    #endregion
 
    #region Server-side Code
 
    private static int count = 0;
 
    protected void DataPortal_Execute()
    {
      Thread.Sleep(2500);
 
      count++;
      Result = count;
    }
 
    #endregion
  }

and make the rule run asyncronously
  public class CPUIntensiveRule : PropertyRule   {     public CPUIntensiveRule(IPropertyInfo primaryProperty, string errorMessage)       : base(primaryProperty)     {       MessageText = errorMessage;       CanRunAsAffectedProperty = false;       CanRunInCheckRules = false;       IsAsync = true;     }     protected override void Execute(RuleContext context)     {         CPUIntensiveCommand.BeginExecute(string.Empty, context, Complete);     }     private void Complete(object sender, DataPortalResult<CPUIntensiveCommand> e)     {       var context = (RuleContext) e.UserState;       if (e.Object.Result % 3 == 1)         context.AddErrorResult(PrimaryProperty, GetMessage());
context.Complete();
     }   }

And you should also look at Csla.Xaml.ViewModelBase to show an indicator to the user that
the object is "busy" and get "bindable" meta properties (CanSave, IsDirty etc).

Copyright (c) Marimer LLC