Adding Business Rule for parent-child-grandchild relationship

Adding Business Rule for parent-child-grandchild relationship

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


timchandler posted on Wednesday, December 16, 2009

Hi all,

I need to implement a business rule where the child needs to ask the grandparent object to validate something in order to determine if it is valid.

Here's the scenario:
ObjectA
----------
...some properties...
ObjectB

ObjectB
-----------
Collection of ObjectC

ObjectC
-------
...some properties...
ObjectD

ObjectD
----------
Collection of ObjectE

ObjectE
-----------
...some properties...
FromValue
ToValue

The rule that needs to be implemented is that, the FromValue and ToValue should not overlap any of the other From/ToValues in ObjectA.

I was trying to add a Business Rule to ObjectE. All I can gain access to though is the parent of ObjectE, which is ObjectD. But I can't get access to ObjectA, which I could use to iterate through the collection to verify the From/ToValues are unique for the new ObjectE I'm creating, or editing.

Is it possible to have the rule written at ObjectA's level, but have the changes in the properties at the ObjectE level trigger it?

Probably not the clearest example, but I'm still working on cup-of-coffee #1. Let me know if I can provide more detail.

Thanks!
Tim Chandler

ajj3085 replied on Wednesday, December 16, 2009

Hmm... when this kind of thing comes up, I tend to think it should be the grandparent doing the validating.

Think of it this way... a child can't determine if its valid or invalid based solely on its state; who "owns" it makes up part of the valid / invalid puzzle, so an invalid object could theoretically be made valid if only it were owned by another root. 

Conversely, part of the root object's state includes the child's state.. so it's in a perfect position to validate or not.

So the last part of your question, can something changing in E trigger something in A.  I believe the answer is yes, and I believe you should be able to determine that using the OnChildChanged method.  That should bubble up all the way to the root.

HTH

Andy

timchandler replied on Wednesday, December 16, 2009

Yes, that makes a lot of sense to me. It sounds like what I'm looking for.

So, do I just need to override OnChildChanged at ObjectA's level, or do I need to do it at all the subsequent classes (ObjectB, ObjectC, ObjectD) as well? In other words, does the overall parent object detect a change way down way down at the great-great-grandchild level?

Tim

ajj3085 replied on Wednesday, December 16, 2009

I believe OnChildChanged will bubble all the way to the top of the hierarchy, so I think just doing the work in object A would be fine.

timchandler replied on Wednesday, December 16, 2009

Yep, that seemed to work.

Now the questiion is, should I somehow try to say the Business Rule is invalid at the ObjectE level (where the change was made) or at the top level? I'd like to do it at the bottom level so I can reflect isValid logic near the source of my textboxes where I added the invalid code.

Is there a way to manually insert a rule into the BrokenRule collection at the (ultimate) child level?

Tim

rxelizondo replied on Wednesday, December 16, 2009

timchandler:
I was trying to add a Business Rule to ObjectE. All I can gain access to though is the parent of ObjectE, which is ObjectD. But I can't get access to ObjectA, which I could use to iterate through the collection to verify the From/ToValues are unique for the new ObjectE I'm creating, or editing.
If you want to leave the rule in ObjectE:

1) you could do:

((ObjectC)((ObjectD)this.Parent).Parent).Parent   ...... etc from  ObjectE  until you get to the root object.

2) You could also do what Andy is suggesting but instead of checking the rule in ObjectA, just iterate through all the ObjectE in the bottom collection from ObjectA itself calling a special rule checker method on ObjectE

The method in ObjectE could look something like below, you can pass the ObjectA (or the key field) to that method like:

class ObjectE
{
   .......

   internal void FromToRuleChecker(ObjectA objA)
   {
      this.ObjARef = objA;
      this.ValidationRules.CheckRules(FromProperty);
      this.ValidationRules.CheckRules(ToProperty);
   }
}

In this case, your "To" and "From" rules would use the "ObjARef" object to obtain the values needed for validation. You may want to mark "ObjARef" as [NotUndoable()] and [NonSerialized()] or just set it to null at the end of the method.

forrest replied on Thursday, December 17, 2009

Same question for my project.

Root Class:
public class Order : BusinessBase
{
   private string customerNo;
   private OrderItems items = OrderItems.NewItems();

...........
}

public class OrderItems : BusinessListBase<OrderItems,OrderItem>
{

    private OrderItems()
    {
        MarkAsChild();
    }
.....
}

public class OrderItem : BusinessBase
{
     private string modelNo;
    
     private OrderItem()
    {MarkAsChild();}

...............
}

project's logic need some customerNo can not order some modelNo,so OrderItem class must validate modelNo with this order's customer info.

OrderItem's parent is OrderItems,but can not get OrderItems's parent object:Order,so can not get the customerNo.


how should i do ?

thanks

ajj3085 replied on Thursday, December 17, 2009

Ya, if the error provider needs to work for the grandchild, you pretty much are tied to doing #1.

I wouldn't do it quite that way though; since the post below asks about line items, and I've dealt with a line items use case similar, I had the a property on the line item which got its parent.

Its parent then got its parent, so I wasn't changing together all those cases and breaking encapsulation too bad.  The line item simply asked its parent for the Document (which is the base for Quote, Invoice, etc), which "knew" how to ask its parent or answer the question directly.

forrest replied on Monday, December 21, 2009

I set a _parent property in BusinessListBase class:
public class BinDataApplyItems : BusinessListBase<BinDataApplyItems>
{
        private BinDataApply _parent;

        public BinDataApply Parent
        {
            get { return _parent; }
            //set { _parent = value; }
        }
}
      
root class:BinDataApply:
public class BinDataApply : BusinessBase<BinDataApply>
{
    private BinDataApplyItems _items;

private void Fetch(SafeDataReader dr)
{
        ......
            //Items Fetch
            if (dr.NextResult())
            {
                _items = BinDataApplyItems.GetItems(dr,this);
            }

}
}

when root class factory methods call the BusinessListBase class,root class pass itself to the BusinessListBase.

so the BusinessListObject object can get it's parent : BinDataApply Object.

BusinessListObject object's children class:BinDataApplyItem
because BinDataApplyItem's base class BusinessBase's constructor call the AddInstanceBusinessRules()/AddBusinessRules();

so i just define a common function in commonrule.cs,and in BinDataApplyItem class build the RuleArgs arguments,and pass to the validation method.


        public static bool ModelNoValidation(object target, RuleArgs e)
        {
            string modelNo = (string)Utilities.CallByName(target, e.PropertyName, CallType.Get);
            if (string.IsNullOrEmpty(modelNo))
            {
                e.Description = string.Format(Resources.StringRequiredRule, Resources.ModelNo);
                return false;
            }

            ProductInfo product = ProductInfo.GetProduct(modelNo);
            if (product == null)
            {
                e.Description = string.Format(Resources.ObjectNotExistException, Resources.ModelNo, modelNo);
                return false;
            }

            ProductValueRuleArg args = (ProductValueRuleArg)e;
            //to do more validation


            return true;
        }



in BinDataApplyItem class:

        private bool ModelNoValidation(object target, RuleArgs e)
        {
            BinDataApply apply = ((BinDataApplyItems)this.Parent).Parent;
            ProductValueRuleArg e1 = new ProductValueRuleArg(e.PropertyName, e.PropertyFriendlyName, apply.CustomerNo);

            bool valid = Validation.GeneralRules.ModelNoValidation(target, e1);
            if (!valid)
            {
                e.Description = e1.Description;
            }

            return valid;
        }


by this way I can solve the question

rsbaker0 replied on Tuesday, December 22, 2009

Incidentally, I believe both BusinessBase and BusinessListBase classes maintain a reference to their Parent object, so it's possible to walk arbitrarily far up the parent heirarchy.

For this to be convenient, I think you have to derive your own common intermediate base class from both of them, derive all your objects common base, and expose an interface (e.g. IChild, etc.) that will return the Parent property.

Then you can write something handy that looks like this that can find an ancestor object of a specific type anywhere in your object graph from any object...


///
/// Gets the first ancestor object of the specified type
///
/// Matching ancestor, if found.
public static object GetAncestor(object current, Type findType)
{
object found = null;
while (current is IChild && ((IChild)current).GetParent() != null)
{
current = ((IChild)current).GetParent();
if (findType.IsAssignableFrom(current.GetType()))
{
found = current;
break;
}
}

return found;
}

forrest replied on Tuesday, December 22, 2009

hi,rsbaker0

thanks your reply,can you explain it more detailed?

thanks a lot!

rsbaker0 replied on Tuesday, December 22, 2009

^^^^^

I'm at home and don't have my source handy, but both BusinessBase and BusinessListBase have a Parent property.

The IChild interface above simply includes a method called GetParent(). I have a common base class (call them MyBusinessBase and MyBusinessListBase for the sake of the discussion) that implement the IChild interface, and the GetParent() implementation just returns the value of Parent. All of my BusinessBase or BusinessListBase type objects derive from one of these two classes, so the code above can always "find" a parent object of a particular type just by walking up the objects returned by the GetParent() calls and testing for the desired type.

Most of the code is above -- all that is missing is the GetParent() implementations, but they basically just look like "return Parent;"

Copyright (c) Marimer LLC