Design Approach Sanity Check - Parent/Child References

Design Approach Sanity Check - Parent/Child References

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


SonOfPirate posted on Thursday, April 26, 2007

I had started typing a much bigger post when it occured to me that my problem boils down to one concept: managing references to parent objects from child objects.

I have an application with a root object, several child collections with grandchild and great-grandchild collections as well.  I am using the lazy-load approach for the child collections rather than having multiple recordsets returned with the parent (as is shown in the ProjectTracker application).  So, for the root object to instantiate its child SolutionProjectCollection, for instance, it uses the following code:

return SolutionProjectCollection.GetSolutionProjectCollection(this);

The key part of that is the argument being passed to the factory method - a reference to the parent object.

I need this object so that I can use the unique identifier as a parameter in the Select query to retrieve the collection items as well as to set a reference in each child item to its parent.  The question is, what is the best way to accomplish this?

I know that we have the SetParent method available to us in the base classes and it is called automatically in the BusinessListBase.InsertItem and OnDeserializedHandler methods.  However, if I am understanding how things work correctly, the InsertItem method is called on the remote server as the child objects are instantiated and added to the collection.  The OnDeserializedHandler is called when the object is deserialized back on the local system.  Both of these also set the child object's parent property to the collection and not the parent object.

So, in order for me to get to the actual parent object, and not the collection, from my child object, I would have to call Parent.Parent (assuming I added a Parent property to the collection).

My other option is to pass the reference to the parent object through the data portal as part of the criteria object (as opposed to just the parent's unique identifier which is used for the SQL).  But doesn't this add overhead as the entire parent object will now get serialized and passed to the data portal?  Especially in my case where each of the root object's child collections could have several hundred items and be part of what is serialized when a grandchild collection is created (because of the child's reference to its parent).

So, I am wondering what the recommended approach is to accompodate this.  Again, it boils down to these two elements:

  1. I need to be able to pass the parent object's unique identifier to the child collection's factory method (and on to the DataPortal_Fetch method as part of the criteria object).
  2. And, I need to establish a reference for each child object created back to this parent object.

Thoughts???

 

RockfordLhotka replied on Friday, April 27, 2007

Why do your children need a parent reference?

Often, a better solution is to have the child raise an event, which the parent (collection) can handle and re-raise as a list event, which the parent (root) can handle to take action.

This event model is cleaner than back-pointers because it is a little easier to manage overall, and it avoids the chance of tighly coupling your children back to the parent. This way the children remain blissfully unaware of their parent's type or anything about the parent really.

But if you do want/need a parent reference, I'd recommend sticking with the SetParent() technique I use in the framework. The reason is, as you say, because there are multiple points in the child's lifecycle where the parent reference must be re-instated, and only one of them can be solved through a constructor or factory method. The others (most notably OnDeserialized()) must be solved against a pre-existing object instance.

And I do think I'd stick with the Parent.Parent scenario - though you may encapsulate that in a private/protected property in the child object as something like ParentOrder (or whatever type your parent object might be).

SonOfPirate replied on Friday, April 27, 2007

Yea, I was thinking of posting an example of what we are trying to do so that what I was asking had a little more "meat" to it.

Essentially, I am developing a highly performant application that has the following basic structure:

Portfolio
  ProjectCollection
    Project
      TaskCollection
        Task
          (Sub)TaskCollection
            (Sub)Task
              and so on...

The heirarchy is infinite. As a result, each collection is considered a "root" collection in that it is responsible for loading all of its children.  The collection's factory method receives a reference to the parent object and has that available to set each child object's parent property.  But, as mentioned, the core classes set the collection as the parent.

The reason we need the reference to the parent is because we are "cascading" data down the heirarchy. This is top-down so we can't use events since the change will be at the parent level and not coming from the child.

For example, each Project in our Portfolio has a StartDate.  When a Task is added to the Project, it defaults to the same StartDate as the parent Project.  However, through the use of nullables, we allow the user to "override" this default by explicitly setting the Task's StartDate to some other value.

I'm sure your first response is that this is not a problem, simply set the child Task's StartDate property equal to the parent Project's when it is created and added to the collection.  Yes, we could do this except for the following:

1. If the parent Project's StartDate is changed and the child Task's StartDate has NOT been explicitly set (overriden), then the change should propogate to the child.

This could be accomplished using a foreach loop to iterate over the child objects, resetting each object's StartDate as incurred.  There are two problems with this: one, we would need to know whether or not the child has overriden the value before setting since we don't want to undo an explicit value (unless... see below).  And, two, performance.  If we cascade the change in this manner, we would be iterating over countless child objects which would then have to duplicate the process for its child objects and so on and so on.  Furthermore, by doing this we've made changes to all of those child, grand-child, great-grandchild, and so on, objects - requiring significantly more time to save to the database.

This is all mitigated by having all child objects use the following type of get accessor:

public System.DateTime StartDate
{
    get
    {
        if (_startDate.HasValue)
            return _startDate.Value;
        else
            return _parent.StartDate;
    }
}

Following this approach, the call climbs the object model until it reaches some point where the value was explicitly set.  When the value is set, only that object needs to be persisted to the database.

2. Validation.  We can't allow the user to set a StartDate on a child object that is before its parent's StartDate.

To implement this, we need to be able to have our rule method reference the parent object's value to make this determination.

 

Hopefully that describes what I am doing a little better.  I have to give more thought to the Parent.Parent issue.  Is there any other reason for having the child reference the containing collection besides notifying the collection when the object is deleted directly (as opposed to through the collection)?

Thx

 

RockfordLhotka replied on Friday, April 27, 2007

That helps a lot, thanks. And I see where the parent reference is the easiest solution here. This is not unlike the way WPF is designed, at least in terms of the DataContext, and it is a very elegant solution.

I wouldn't mess with the CSLA collection Parent reference. It is used in one way now, but I may broaden its use in the future. You are best off, if you want to skip the collection level, creating your own parallel parent references.

But I think you can use the existing Parent reference scheme.

In your model, the child wants to cascade its call to the parent. But its parent is a collection. No problem! Just implement StartDate on the collection too:

public System.DateTime StartDate
{
  get
  {
    return ((IWhatever)Parent).StartDate;
  }
}

I'd use interfaces to make this smooth - all the way up. So all your objects (BB and BLB) would implement one or more interfaces for use in implementing these cascading properies.

But since the collection can never hold a date, all it needs to do is cascade the call up. Very simple, and you get to leverage the existing parent reference implementation.

(I recommend this, because I'm seriously considering an optional enhancement to CSLA to help manage child objects/collections - which would include both automatically setting up Parent references all the way through, and automatically handling PropertyChanged/ListChanged event cascading all the way through, and if I do get this into the framework, you'd benefit Smile [:)] )

Copyright (c) Marimer LLC