Parent/Child many levels deep architecture

Parent/Child many levels deep architecture

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


SteveChadbourne posted on Monday, July 24, 2006

Hi

I understand the way that root and child object hierarchies work under csla, like the project tracker example, but what about hierarchies that are many levels deep? Should the hierarchy be broken in some way for performance reasons?

As an example, say I had 3 classes: petshop, cage and animal. I start out with a petshop class, a cagelist class and a cage class. I write a stored procedure to get a petshop and all associated cages. But what about animals? Does each cage class contain an animal collection? That means I have to load all animals for all cages when I might only be interested in one page. Where do I de-couple the cage and animal classes?

Steve

JonM replied on Monday, July 24, 2006

There are no easy answers to this situation.  It is easy to use SQL to pull back parent and child resultsets using a single stored procedure call.  However, I have not found any similar way to pull down multiple nested results.  There is some code floating around out there where all of the grand-children items (or animals in your case) get pulled in by the child-object and then it gets run through another routine that sorts out what item belongs to what and adds them there, it is a bit messy and a bit inconsistent with the general CSLA design, but it can work.  I have generally found in most of my designs that the need for a grandchildren can be avoided with better design choices.  Sometimes, they are still needed, so I will put db access code in the grandchildren so they can load themselves.  I know it is a performance hit, but I try to avoid having large resultsets.

Let's think about the design here.  Do you really need to be able to load a Petshop + CageList + AnimalList at the same time?  That's basically loading in all the data for the entire petshop.   You could break them up.  Why not make the animal list a "editable root collection" and disconnect it entirely from the cage class?  You could add a couple of read-only fields to the cage class like 'TotalAnimals' so that a user looking a list of cages would know something about the animals without loading all the details.  Then if the user want's more info have them click a 'Details' button that loads another form that creates and populates a new AnimalList collection using the CageID as the criteria.  Anyway, it's just a thought.  Think about how the users will interact with the software and let that help form your object design.

ajj3085 replied on Tuesday, July 25, 2006

Just wanted to say that I think Jon is dead on here.  You probably don't need to edit a petshop + cages + animals as one large transactional object.  Its more likely that you'll be editing much smaller chunks of data.  For example, if you're adding an animal, do you need to change any data which belongs with petshop (like the name)?  Likely not.

HTH
Andy

Tom Cooley replied on Tuesday, July 25, 2006

While both Jon & Andy are right and you should avoid deep hierarchys, there are occaisions when your use cases dictate them. So, here is a technique I've used to assist with this. Using your example, the actors in this use case are:
PetShop (ER)
Cages (ECC)
Cage (EC)
Animals (ECC)
Animal (EC)

Given that your PetShop Fetch stored procedure returns three result sets, one with a single row for the PetShop, one for the cages with many rows, and one for the animals with many rows as well. The animals result set must have the foreign key to the cages for this to work.

The DataPortal_Fetch does its normal thing, executing the command and extracting its state from the first result set, then passing the second result set to a factory method in Cages like:

this.Cages.GetCages(dr);

But then it does the following:

dr.NextResult();

this.Cages.GetCageAnimals(dr);

This is another factory method in Cages class but it iterates through the Animals result set something like this:

internal void GetCageAnimals(SafeDataReader dr)
{
    while (dr.Read())
       {
          Guid cageId = dr.GetGuid("CageId");
          Cage cage = this[cageId];
          if (cage != null)
             {
                cage.Animals.GetAnimal(dr);
             }
       }
}

This "distributes" the animals from the single result set into the Animals collection of the appropriate cage. Now, I wrote this straight into the forum so there may be a few typos or inaccuracies, but hopefully you get the gist of the idea.

I've seen other examples where people use datasets with relations to do the mapping, but I didn't really care for the approach. This was fairly simple to do. The only design stretch is that you're placing the responsibility of loading the grand children on the child collection that would not otherwise have had to be coupled to that class. To me, it was a small concession. I've only had to use this once and it has held up well.

Again, I try to avoid this design if possible, but it's nice to have a strategy for it when necessary.

Tom


ktweedy replied on Tuesday, July 25, 2006

My thought about this is using a DataReader makes this difficult since you have to get all the result sets for each parent object to iterate over.

Seems a DataSet with views or selecting DataRow[] would be an option for these situations.  Each each parent object could perform a select on a DataTable to get its children rows and if it is a grand parent maybe also pass the DataSet or some subset of data to the child so it has its children data.  For me I suspect I sould just pass the DataSet down the tree and let each parent find its children as needed.

Getting the DataSet would let you get all the data you need in one database call.

DogSpots replied on Tuesday, July 25, 2006

I agree with ktweedy.  Using a DataReader makes it difficult to get the entire result set in one database call.  Using a DataSet is much easier.

There's some information about using DataSet, DataRow[] and SafeRowReader in the old forums.  One particularly helpful thread is: http://groups.msn.com/CSLANET/general.msnw?action=get_message&mview=0&ID_Message=1304

I've been using this approach based on the examples in that thread with very good results. 

 

vargasbo replied on Tuesday, July 25, 2006

I deal with this issue all the time. The patterns I've used is to get one depth below my current object.
So when you first load the Petshop, you would pull back all its cages. Then, when you selected a specific cage, then you would go out and load all its animal(s). I'm sure you get the idea, this is better than having to load a huge dataset. Cons, the application might be a little chatty, but it works great when you have to work with objects (graph) that are five nodes deep.

SteveChadbourne replied on Tuesday, July 25, 2006

Thanks people.

My app has an object design 5 nodes deep so I'm not looking to load all the data up front. The issues was when to load the next level. The csla paradigm seems to be that a parent loads its children so the petshop would load then cages then getcage would load all its animals. I wasn't sure whether to do this or just load the animals when they were needed. I'm leaning towards vargasbo's approach.

Copyright (c) Marimer LLC