Parent Child business rule - location of the rule

Parent Child business rule - location of the rule

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


richardb posted on Thursday, April 23, 2015

I have an Account model with an editable list of users which I can assign to the account.

I need to check for duplicates as I'm adding a user into the list, plus I need to check the database to make sure that the user has NOT been assigned to another account.  I'm not trusting the UI to make sure it filters out available users.

BTW this is an MVC application, so any errors will be displayed when the form is posted back.  I don't need immediate feedback if I try and add a user already in the list (in memory) or if they've since been allocated to another account.

So, while I can do a list.Contains(userId) call in the list.Add method for the in-memory check, I need to run an expensive rule to look at the database before the save occurs.

Is it just a case of creating a "command style" object like ExistsCommand and use that to create a custom rule in the parent Account model which loops through all the children making sure they are free to be assigned?  I think that's the way to go, just need a nod I'm on the right track.

And I'll set the priority of this so it only runs if all the other rules are checked and passed first.

PS.
I don't update the children, users are either added to or removed from the account. 

Thanks,

Richard.

richardb replied on Thursday, April 23, 2015

Although maybe for the database exist command I just need to the rule on the child object, as I won't need to check previously saved children.  But will still need to check for duplicates being added on the parent.

richardb replied on Friday, April 24, 2015

Ok what I did was.....On my parent object was put a check to make sure we do not have any duplicates - ProjectTracker sample helped me out here - Thanks.

 

        protected override void AddBusinessRules()
       {
           base.AddBusinessRules();

            //Check we don't assign the same user twice.
           BusinessRules.AddRule(new NoDuplicateUserAccounts { PrimaryProperty = AccountUsersProperty });
       }

        private class NoDuplicateUserAccounts : Csla.Rules.BusinessRule
       {
           protected override void Execute(Csla.Rules.RuleContext context)
           {
               var target = (AccountEdit)context.Target;
               foreach (var item in target.AccountUsers)
               {
                   var count = target.AccountUsers.Count(r => r.UserId == item.UserId);
                   if (count > 1)
                   {
                       context.AddErrorResult("Duplicate users on account not allowed");
                       return;
                   }
               }
           }
       }

        protected override void OnChildChanged(Csla.Core.ChildChangedEventArgs e)
       {

            //What type of child are we dealing with?
           if (e.ChildObject is AccountUserEditList)
           {
               //Recheck our rules.
               BusinessRules.CheckRules(AccountUsersProperty);
               OnPropertyChanged(AccountUsersProperty);
           }
           base.OnChildChanged(e);

     }

 

 

And then the check to make sure a user was not already assigned was implemented on the child object like this;

protected override void AddBusinessRules()
       {
           base.AddBusinessRules();

            //Check userId can be assigned.
           BusinessRules.AddRule(new UserAccountExists { PrimaryProperty = UserIdProperty });

        }

 

       class UserAccountExists : BusinessRule
       {
           protected override void Execute(RuleContext context)
           {
               var thisUserAccount = (AccountUserEdit)context.Target;
               var cmd = new AccountUserExistsCommand(thisUserAccount.UserId);
               cmd = DataPortal.Execute(cmd);
               if (cmd.AccountUserExists && thisUserAccount.AccountId != cmd.AccountId)
               {
                   context.AddErrorResult("User already assigned to a different Account - Cannot assign them to this account.");
               }

                //IMPORTANT indicate rule is complete
               context.Complete();

            }

        }

 

My only question remains, is this the up-to-date way of implementing the business rules 4.5.x style?  Guess I could async the AccountUserExistsCommand perhaps - any benefit to doing that here?  

Is there a "latest document" that has all the ways of doing business and validation rules?  I have the book and pdf guides of course.

Thanks,

Richard.

JonnyBee replied on Saturday, April 25, 2015

It should work just fine in the latest version. 

Yes, you might want to consider using async command - it may rune significantly faster overall (and in parallell).

And it is only for async rules that you must make sure to call context.Complete(). 
(the rule engine will call context.Complete() when rule.IsAsync is false) 

For my own coding preference I prefer to use InputProperties and have the rule engine retrieve the actual value.
This way - the Execute method does not have to know the type (like AccountUserEdit) and may be more general purpose. 

I would probably also change the name of the rule to NotUserAlreadyAssignedOtherAccount or similar.

Copyright (c) Marimer LLC