Calculating Totals in Collections

Calculating Totals in Collections

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


Cslah posted on Thursday, November 01, 2007

I've got a collection of items, each item has a weight, price, etc. At the collection level I've created the Total properties (TotalWeight, etc).

1000 items is common in our collections and we actually have everything performing beautifully when binding to the grid.

The problem is when we start filling out the quantities and calculating the totals, it's painfully slow as the method that calculates has to traverse thousands of items (even though not all of them have quantities). It's about a 1/2 second to calculate them, which makes rapid data entry out of the question.

I'm not sure this is really a Csla thing, but more of a design question maybe? I have tried turning RaiseListChanged off and back on but it doesn't seem to do much.

One option is to make the method that calculates these items operate in the UI since it's calculated on the fly anyways, and have that method operate in a different thread that sets the total on the textbox when it's done? That way the UI won't seem to lag.

Was curious what other people do if they've seen this problem?

Thanks,
Brian

ajj3085 replied on Thursday, November 01, 2007

There's a bugfix in 3.0.3 (test) which may help this.  You may want to try upgrading and seeing if changing the RaiseListChangedEvents works or not.  I wouldn't think it should cause that much of a deal to iterate over 1000 items and add some numbers.. computers are supposed to be good at that :-)

But if there are a lot of ListChanged events firing for some reason, that WILL cause alot of speed issues.  I've had to resolve these myself, although I'm wondinger if 3.0.3 will help (I thought setting RaiseListChangedEvents on the BindingSource would propigate the call to the underlying list, but it doesn't... maybe I was seeing a side effect of a bug in BLB that wasn't honoring that flag).

Cslah replied on Thursday, November 01, 2007

Thanks for the tip.

I've just updated my project to 3.0.3 and don't really notice a difference.

What I've done for now is not databind the total property (from the parent object) to the label on my winform. Instead I trap the bindingsource ListChanged event on my form and manually calculate the totals. This is still a tiny bit slow but much better than before. In either case I had to call .ResetBindings(False) on the parent to get the totals to refresh. I do have a BindingSourceRefresh on my winform but it doesn't seem to work for the child collection changing.

So, I guess some of the speed problems are in databinding?

Thanks again,
Brian

ajj3085 replied on Thursday, November 01, 2007

Brian,

Well, even with the changes in 3.0.3, i'm not sure setting the BindingSources RaiseListChangedEvents will propogate to the underlying list.  In my case, I set both the lists and Bindingsource's RLCEs = false.  If you just set the BS, it won't raise events, but it will still receive events from the list.  That will slow things up.

Your best be is to change your BOs programatically, without using databinding, to see if databinding is the culprit or not.  If you change the child then immediately read the total and time it, you'll see where the problem is.

Andy

mr_lasseter replied on Thursday, November 01, 2007

You could always raise an event in your child class when the wieght changes.  This event could provide the delta of the weight change.  Then in your Collection class you either add or subtract the changes to TotalWeight.  With this approach you don't have to worry about traversing through the list except on the initial load. 

ajj3085 replied on Friday, November 02, 2007

That falls apart as soon as you turn off RLCEs to do some updates to multiple rows at once. 

RockfordLhotka replied on Friday, November 02, 2007

ajj3085:
That falls apart as soon as you turn off RLCEs to do some updates to multiple rows at once. 

Not necessarily. Turning off ListChanged has no impact on the PropertyChanged events of the child objects.

So if the recalc is triggered by PropertyChanged (or the custom event idea someone mentioned earlier, or the callback idea William suggested), the list object would still get that notification and would know to recalc.

ajj3085 replied on Friday, November 02, 2007

I meant that listening for the ListChanged event to update the data would not work if you turn off RLCEs.  Listening to the PropertyChanged events should wok though.

William replied on Thursday, November 01, 2007

A suggestion by coupling the collection and the item a bit. See if this works. // ItemCollection public class ItemCollection : BusinessListBase { private decimal _totalWeight; public decimal TotalWeight { get { return _totalWeight; } } internal void RecalculateTotalWeight(decimal offsetValue) { _totalWeight += offsetValue; } } // Item public class Item : BusinessBase { [NotUndoable] private ItemCollection _parent; public decimal Weight { get { return _weight; } set { if (_weight != value) { _parent.RecalculateTotalWeight(-_weight + value); _weight = value; PropertyHasChanged("Weight"); } } } } Regards, William

RockfordLhotka replied on Thursday, November 01, 2007

Honestly though, if recalculating takes .5 seconds, then recalculating from scratch every time may be unrealistic.

Is it possible to keep a "running total"? In other words, if the parent object were notified each time a specific child's specific property was changed, could it do an incremental adjustment?

Presumably this would require knowing the previous value too - but it may be worth coming up with a way to provide that. The

Suppose the child had a Friend/internal PreviousWeight property?

public class Child : ...
{
  private int _weight;
  private int _lastWeight;
  public int Weight
  {
    get { return _weight; }
    set { _lastWeight = _weight; _weight = value; PropertyHasChanged("Weight"); }
  }
  internal int LastWeight
  { get { return _lastWeight; } }
}

Then your collection could update the TotalWeight value by backing out the LastWeight and adding in the new Weight each time it handles PropertyChanged for Weight. That'd be a lot faster than trying to recalc from scratch every time.

William replied on Thursday, November 01, 2007

Sorry. There is text formatting issue in my previous post.

A suggestion by coupling the collection and the item a bit. See if this works.

// ItemCollection
public class ItemCollection : BusinessListBase<ItemCollection>
{
    private decimal _totalWeight;
    public decimal TotalWeight { get { return _totalWeight; } }

    internal void RecalculateTotalWeight(decimal offsetValue)
    {
        _totalWeight += offsetValue;
    }
}

// Item
public class Item : BusinessBase<Item>
{
    [NotUndoable]
    private ItemCollection _parent;

    public decimal Weight
    {
        get { return _weight; }
        set
        {
            if (_weight != value)
            {
                _parent.RecalculateTotalWeight(-_weight + value);
                _weight = value;
                PropertyHasChanged("Weight");
            }
        }
    }
}



Copyright (c) Marimer LLC