An issue that came up while reading CSLA.NET 1.x that has resurfaced reading 2.0 has to do with the way the n-level undo features are implemented for collections. In particular, the fact that the current process does not accurately return the collection to its previous state when CancelEdit/UndoChanges is called. Perhaps an example will illustrate...
Assume there is a collection that is pre-populated (possibly from a database) with 3 objects, say an invoice object's LineItems collection. During editing, the second of the items is deleted (removed) then the change is cancelled as follows:
LineItems.BeginEdit();
LineItems.RemoveAt(1);
LineItems.CancelEdit();
It is my belief that the collection should be returned to the exact same state as before this code executed; however, this is not the case. If we execute the same code with two additional statements as shown here:
myItem = LineItems[2];
LineItems.BeginEdit();
LineItems.RemoveAt(1);
LineItems.CancelEdit();
myItem = LineItems[2];
you will find that myItem is set to different objects because we do not restore the collection completely - the relative indexing of the items in the collection has changed. What you will find is that the first time we set myItem the collection returns the third item (as expected). But, the second time, we find that myItem is the second item (the restored item)!
After reviewing the code, it is obvious that this is happening because the UndeleteChild method simply re-Adds the item to the collection - to the END of the collection.
This violates the tenet that the collection's state would be restored as it was before the edit. If this were the case, then myItem would reference the same object both times it is set in the above code.
I think I see the head-aches involved, but why not serialize the collection and have the collection maintain its own snapshots just like business objects? We would have to eliminate the cascading calls, obviously, and may result in much 'heavier' applications, but at least this would ensure that the indexing/positioning of items within the collection was restored. I'm throwing this out for what I hope to be a productive discussion.
BTW - this is most noticable when you bind the collection to a grid-type control, remove an item from anywhere in the middle of the list then undo the change. You'll see the removed item reappear at the bottom. Not exactly what was in mind or expected.
Thanks in advance to all who contribute on this.
The fact that you can use collection items indexes in your code does not mean that you should. Very often you cannot or do not want to control the indexes (because any code that uses your collection can add/remove items at will).
------
BTW, in pre-.Net times many VB developers (myself included) learned not to trust collection items indexes; for example, this is what VB6 Component Tools Guide says in Object Model Creation Guidelines\The Visual Basic Collection Object :
Important: Collection objects maintain their numeric index numbers automatically as you add and delete elements. The numeric index of a given element will thus change over time. Do not save a numeric index value and expect it to retrieve the same element later in your program. Use keys for this purpose.
------
Writing code without using indexes may not be convenient, but it is definitely possible.
I'm currently suffering a problem where I have a detail view with a child collection displayed in a grid view. It's similar to the ProjectTracker example.
The problem I seem to have at the moment is such:
I start with two items in the grid.
I unassign both.
Then I press Cancel which will call CancelEdit(); This works and both items are replaced.
I then unassign one item and press Cancel.
This item does not get replaced.
Any ideas?
This initial post got me thinking. It probably would be possible to maintain the original order on an undo - but there'd be some serious overhead to make it happen. On the surface it seems like you could just keep the original position of an item when it was "removed" so you could put it back in that spot - but that ignores the fact that adds could happen in there too. So then you'd have to track both adds and removes and the order in which they occur so you could undo them in order. Obviously that's not realistic.
So instead, you could have BeginEdit() create an array/list that mirrors the actual list and deletedList contents - referencing the same objects. These lists (main and deleted) would be the "state" of the collection. On an ApplyEdit() you'd discard these lists, but on a CancelEdit() you'd use these lists to restore the actual list and deletedList to match. The deletedList is easy - you'd simply replace the current one with the previous one. But you can't do that for the actual list itself, so this means you have to walk through the list item-by-item to make sure it matches.
Of course you could Clear() the current list and just reload it - but for a lot of items that could be quite slow, so I think you'd have to go item-by-item and resolve whether they match, and if not how to make them match in a relatively efficient manner.
In the end, this adds up to a lot of overhead - but it seems (conceptually) like it would be possible. It isn't something I anticipate looking at in the near future, because I think a simpler answer is to use SortedBindingList to provide a view over the real collection instead.
Copyright (c) Marimer LLC