Whose responsibility is it?

Whose responsibility is it?

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


SonOfPirate posted on Tuesday, June 05, 2007

I have an application where I have to keep track of an object's child items in two collections.  One collection is the primary collection and is used to persist the objects, etc.  The other is essentially used to "bucket" or categorize the objects for other purposes (statistical, logical, etc.).  I am wondering what the most recommended approach is and, ultimately, what suggestions you have about which object should be in charge of maintaining the second collection.

The key is that when I add or remove an item from the primary collection, it needs to also be added or removed from the "bucket" collection - which is a multimap (collection of lists) simply holding references to the objects contained in the other collection grouped by a key field.

I won't lead the witness too much because I am very interested in hearing your opinions and approaches, so I will leave it at that for now.

Thx in advance.

 

Brian Criswell replied on Tuesday, June 05, 2007

Why not use the FilteredBindingList or ObjectListView to provide your logical views?

SonOfPirate replied on Tuesday, June 05, 2007

Yea, I guess that deserved a better explanation.

The "bucket" collection actually contains "bucket" objects which contain the collection of items that are also contained by the primary collection.  This is because the "bucket" object contains some additional properties that summarize or aggregate the "grouped" items.  Oh, and all of this is in memory at once and dynamic, so we can't have a filtered list that represents bucket1 and a separate one that represents bucket2 and so on.

Let me try to represent what we have going on:

RootObject
    Items As PrimaryCollection (of ChildItem objects)
    Buckets As BucketCollection (of Bucket objects)
Bucket
    SomeProperty
    Items As PrimaryCollection (of ChildItem objects)

When we add a ChildItem to the RootObject, it is added to the PrimaryCollection AND somehow needs to be added to the appropriate Bucket in the BucketCollection (with a new one created if it doesn't already exist).  So, who should be responsible for managing this?

The typical ways of adding a child item would be:

RootObject.AddItem(childItem);
RootObject.Items.Add(childItem);

Obviously the first gives us the ability to call the second plus call Buckets.Add(childItem) if that was the route we wanted to go.  The latter would require us to hook up to ListChanged or some other way to handled when an item is added or removed because we shouldn't, imo, couple the PrimaryCollection to the BucketCollection (or should we)?

The former would require us to hide the Add, Remove, etc. methods from the two collections to avoid inadvertant use.  So, all management functions would have to be handled by the containing object.  But, they would still have to be exposed in order for the containing object (RootObject) to access them - declare them as internal???

Thoughts?

 

 

Brian Criswell replied on Tuesday, June 05, 2007

Sure, but there is no reason why the bucket should not be able to delegate the filtering to another class.

class Bucket
{
    private ObjectListView _filteredItems; // Items that fit in this bucket will go here

    //Constructor that accepts the primary collection of items.  The ObjectListView will be aware of all
    // additions, removals and changes to items in the list and only show the items that pass the filter
    // criteria for that bucket.
    internal Bucket(PrimaryCollection items, string filterString)
    {
       _filteredItems = new ObjectListView(items, "PropertyToSortBy", filterString);
    }

    // Exposes the list of filtered items
    public ObjectListView FilteredItems
    {
       get { return _filteredItems; }
    }

    // Gets the sum of a property on all of items that passed the filter.
    public int Sum
    {
       get
       {
          int sum;

          for (int i = 0; i < filteredItems.Count; i++)
          {
             sum += (int)filteredItems[ i ]["PropertyToSum"];
          }

          return sum;
       }
    }
}

class RootObject
{
    private BucketCollection _buckets = new BucketCollection();
    private PrimaryCollection _items;

    DataPortal_Fetch()
    {
       //Get PrimaryCollection from data source.
       // Create buckets
       _buckets.Add(new Bucket(_items, "PropertyToFilterBy like 'A%'"));
       _buckets.Add(new Bucket(_items, "PropertyToFilterBy like 'B%'"));
    }
   
    // Properties to expose buckets and items
}

SonOfPirate replied on Tuesday, June 05, 2007

I am not familiar with the ObjectListView class.  Can you explain it and where it is located?

It sounds like you are recommending having BucketCollection implemented so that it is responsible for listening for changes in the associated collection.  Then, based on the filter "rules" applied, it will perform an action, such as adding and removing items, as well?

Also, how do we handle a case when a new item is added to the RootObject's PrimaryCollection that doesn't have a corresponding bucket yet?  Ultimately, this is how the bucket objects will come into being:

Likewise when an item is removed.  If that is the last item in the bucket, the bucket should also be removed.

To go with your example, let's say our objects are Person classes with a LastName property.  If we are creating buckets for the last name, our filtering would be on that property and, as you've shown, something along the lines of "LIKE A%".  HOWEVER, we don't know the criteria up-front. It is only as items are added that the criteria is established.  In other words, we wouldn't create 26 buckets with 25 empty if our only item was Bob Smith.  In that case we would only have one bucket defined for the S% items.  Should John Doe be added to the collection, we would then create a bucket for the D% cases.

I guess using a name is a bad example because our filtering would never be as finite as A-Z.  It could be numeric based and of any range.

See my dilemma?

 

Brian Criswell replied on Tuesday, June 05, 2007

The ObjectListView class is a set of four classes that are available in CSLAcontrib.  It behaves like the DataView except it works on lists of objects instead of a DataTable.  So your root object listens to PrimaryCollection's ListChanged event and whenever an item is added (or the corresponding property is changed?) it checks to see if a bucket exists for the corresponding filter criteria.  The RootObject also listens to the ObjectListView's ListChanged property in each bucket and if the .Count property goes to 0, it removes the bucket.

SonOfPirate replied on Thursday, June 07, 2007

This is a great tool.  I like what I've seen so far except that I have a problem when loading my objects from the database.  The reason is that the child collection uses lazy-load to retrieve the data for its child objects.  Because of this, we don't attach the ListChanged handler in our RootObject until AFTER the factory method has completed the DataPortal_Fetch call and already populated the collection.  As a result, our buckets are empty.

Obviously, I can get rid of the lazy-load and have the data loaded with the RootObject, but I'd like a solution that keeps the door open for both options if possible.  This is not the only child collection that we may use this approach for and there's a lot of data, so lazy-loading has been our best solution to improving the responsiveness of each individual operation.

Can you think of a way to accomplish the same thing given a lazy-load approach?

 

Brian Criswell replied on Thursday, June 07, 2007

Try this in your lazy loader:
  1. Load data
  2. Attach ListChanged handler
  3. Add buckets
You will need to manually scan the list when it is first loaded to determine which buckets to create.  You could make the /RootObject's ListChanged handler handle ListChanged.Reset, which would clear your bucket collection, rescan the list, and add the appropriate buckets.  When you first load the list, call your handler manually with the list and a ListChanged event arg that has a change type of reset.

SonOfPirate replied on Friday, June 08, 2007

Two questions about the ObjectListView:

 

Brian Criswell replied on Friday, June 08, 2007

SonOfPirate replied on Friday, June 08, 2007

sorry, one more...  Why not a generic implementation?

 

Brian Criswell replied on Friday, June 08, 2007

Because at the moment you can set the list property to a list of a different type.  The only thing a generic implementation would give you that I can think of off the top of my head is that you would not have to cast ObjectView.Object.

SonOfPirate replied on Friday, June 08, 2007

Brian, thanks for indulging my questions.  This is a great addition to the Csla framework.

 

Copyright (c) Marimer LLC