Hello,
I have a hierarchical grid with 6 bands. The binding source is using a BindingList. The object type in the BindingList has one property that is also a BindingList for the child band. Unfortunately, the binding list in the 6th band may possibly have up to 30000 objects. I have implemented a BindingList that maintains an Index cache to solve the IndexOf bottleneck that occurs for BindingLists with a large number of objects in it. Now the performance bottleneck is the CurrencyManager which basically performs a ResetBinding on a child bindinglist whenever it sees that the parent Current object has changed.
I will go into more detail but basically I am looking for suggestions on how to workaround this issue and maximize performance when a large number of objects in a 6th band binding list are modified.
In the case I am discussing I am modifying a property of each object in a child binding list when a parent property is changed. The editing is done from the grid so the row I am editing is Current. Band 0 only has one row in it, so essentially, if I modify one column in the Band 0 business object, this calls a method for event AfterRowUpdate where I modify a property on every object in Band 5.
All of my objects implement INotifyPropertyChanged and a ListChanged event on a child bindinglist will generate a property changed event for the parent business object. This is because my parent objects show aggregated values from the child objects. I am not using the calculator because that would slow things down a lot. So this is why my parent objects generate a property changed event when the child binding list has changed.
Now everything is fine up until the property changed event is fired on the band 0 object. The BindingList generates a ListChanged.ItemChanged event for the object and the BindingSource interprets this as meaning the entire object changed and therefore the child binding list property must also have changed. It calls InnerList_ListChanged The currency manager generates a OnCurrentItemChanged (because that object is the current item) and this calls ParentCurrencyManager_CurrentItemChanged. This is called recursively down to every current item in the 6 bands doing a ResetBinding on every current bindinglist.
Using a profiler, the ParentCurrencyManager_CurrentItemChanged consists of 96.5% of the time required to do the update. The path to this callback delegate cannot be turned off by changing any flag such as RaiseListChangedEvents.
I consider it a flaw that changing one property of the Current object triggers the CurrencyManager to call SetList for the DataMember property of the child list. I wish it could use the property descriptor supplied in the ListChanged.ItemChanged event args and only call SetList for the child bindinglist if the DataMember for the child list was the property that was changed. Assuming that every property of an object changed when the ListChanged.ItemChanged event is generated basically means that modifying one property of a parent current object invalidates the child lists and if those lists are large then the performance hit is significant.
To update 2870 objects in band 6 took 57 seconds but 55 seconds of this time was just handling the ParentCurrencyManager_CurrentItemChanged code which does absolutely nothing in my case because none of the lists changed, only the contents of one object in the list and this does not require a ResetBindings on the list.
Looking for any suggestions. Only thing that comes to mind is subclassing the CurrencyManager or disabling the property change triggering in my own function that updates these objects and then just doing something else to inform the grid that the business object has changed. The problem is that the listchanged.itemchanged event is informing the grid that that row has changed but its also cause the Currency manager to go through a code path that is wasting too much time doing something completely unnecessary.
Just one more note. I never change a binding list property, only the contents of the binding lists so the fact the currency manager resets the bindings on these lists is unnecessary but I am not sure how to avoid it. Thanks in advance for any advice. I am using Infragistics 8.2 but the same issue would occur if using a later version.
Here is a screenshot of the profiler call tree:
After I wrote this. I took advantage of an interface I added to all my business objects that basically suspends OnPropertyChanged from generating the events until I call a method to resume then. Resuming generates a single PropertyChanged event if any where suspended.
This reduced multiple property change events from the parent object every time the child binding list generated a listchanged.itemchanged event to a single property changed event. So ParentCurrencyManager_CurrentItemChanged was called 42 times instead of 3000 times.
However, I would still have an issue for other grids I have where the last band is populated from shared object updated in a separate model. I have an observable collection for my model and the objects in that model populate band 6. A different code path may update any objects in that model and that update path does not know who the parent object is for any binding lists that may have a reference to the object in the observable collection.
So my question in previous post is still worth asking. How to use a Currency manager in an efficient way. Perhaps a way that doesn't assume that the child binding list changed when it received an item changed event.
Hi Mark,
I can't honestly say I ever ran into this problem, myself. I've never heard of anyone else who had, either. It seems like this is a result of something pretty specific to your application. The fact that your data is so tightly inter-rated and updating a single row updates every row 5 levels down is probably pretty unusual.
Also... the sheer volume of data you are displaying the grid is pretty unusual, as well. I, of course, don't know anything about your application or who is using it, but you may want to consider whether it's a good idea to display such a vast quantity of data to a user all at once. No human user I ever met could handle a 6-level deep hierarchy of data with tens of thousands of rows in any meaningful way. Perhaps you should provide a system of filtering the data that you display so it's presented to the user in smaller chunks. Just a thought.
Regarding the BindingManager, in my experience, it's really not designed to be super-efficient. The ListChanged event only has two parameters, one indicating the type of change and the other supplying an index. So all it can do is tell you that the data in a row changed (ItemChanged) and the index of the row. It cannot supply any information about which field changed, so listeners have to assume that any or all of the fields may have changed. We can all agree that having more information, and knowing which field changed would allow us to make our code more efficient, but that's not the way it works. You would have to ask the folks at Microsoft why they chose to go the way they did. :)
Out of curiosity, is it possible that your BindingList code could be more efficient? For example, when a field value is actually changed, are your property setters checking the old value against the new value to make sure something actually did change? You don't want to be sending INotifyPropertyChanged notifications when a property value is set to the same value it was already set to. This might not help much, but it could stop the chain of events from multiplying in some cases.
It sounds to me like the problem you are experiencing here isn't really related to the grid. Is that the case? If you don't bind the grid and just do the same sort of thing in code, is the performance still an issue? If not, then you might want to consider posting your question in a more general DotNet programming or DataBinding forum - perhaps on Microsoft's site. I'm pretty knowledgable about the grid and making it more efficient, and as part of that, I know a little about the BindingManager, but your question here is about outside the scope of my experience.
Hi David,
Thanks for replying. The binding list actually does provide the property descriptor of the property which changed when it generates the ListChanged.ItemChanged event in this case. Line 1242 of BindingList.cs (method Child_PropertyChanged)
PropertyDescriptor pd = itemTypeProperties.Find(e.PropertyName,
);
// Create event args. If there was no matching property descriptor,
// we raise the list changed anyway.
ListChangedEventArgs args =
ListChangedEventArgs(ListChangedType.ItemChanged, pos, pd);
// Fire the ItemChanged event
OnListChanged(args);
However, does not check the optional PropertyDescriptor property of the ListChanged event. It would have been a nice optimization but its just there for informational purposes and not to drive logic.
I guess I was only posting here in the hopes that there may be another way to bind to the grid such as with an observable collection since the CurrencyManager is flawed in my case.
This parent having 30000 children is actually an exceptional case in my data. Every other parent usually somewhere between 5 and 20 children. However, I have to support this scenario as its obviously unacceptable that the application hangs for 10 minutes if they edit this one parent.
Sorry forgot to answer your question David. Yes the properties all check if it actually changed. So in my case 3000 properties or even 30000 properties can change in one fell swoop. Implementing that suspend notification interface I have on the parent object did the trick.