Edit level issue when child object is removed, then added back to the collection.

Edit level issue when child object is removed, then added back to the collection.

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


dlabar posted on Friday, April 11, 2008

I'm thinking the BusinessListBase needs to override the add method to see if the child that is being added, is already in the deleted list.  If it doesn't, on the AcceptChanges method of the BusinessListBase, the child will have AcceptChanges called twice, once while in the items List, and once while in the DeletedList.  This causes an EditLevelMismatch.

Steps needed to Recreate this issue.

Parent.BeginEdit();
Child myChild = Child.GetChild();
parent.Add(myChild);
parent.Remove(myChild);
parent.Add(myChild);
Parent.ApplyEdit();

Any reason the Business List Base couldn't handle this by overriding the Add function?

public override void Add(<T> item){ 
   if(DeletedList.Contains(item)){ DeletedList.Remove(item)}; 
   base.Add(item);
}

Any other Suggestions?

RockfordLhotka replied on Saturday, April 12, 2008

Good point, I've added this to the wish list as a to-do item.

dlabar replied on Monday, April 14, 2008

There are a couple gotchas with this as well that need to be implemented

Parent parent = Parent.NewParent();
Child child = Child.NewChild();
parent.Add(child);
parent.BeginEdit();
parent.Remove(child);
//User decides they want to undo manually what they just did
parent.Add(child); // At this step, the editLevelAdded value of the child gets incremented from 0 to 1
parent.CancelEdit(); //Now on a cancel the child will be removed from the parent because the add edit level is set at 1

This can be fixed by keeping track of the editLevelAdded as you currently do in the UnDeleteChild() method when adding the child to a class where it is in the deleted list.  Except another issue happens when you do something like this:
Parent parent = Parent.NewParent();
Child child = Child.NewChild();
parent.Add(child);
parent.BeginEdit();
parent.Remove(child);
//User decides they want to add it to a different parent
Parent parent2 = Parent.NewParent();
parent2.Add(child);  // At this step, the editLevelAdded value of the child gets incremented from 0 to 1 because we have nothing in the deleted list to compare it to
//This updates the value in the deleted list of the parent
parent.CancelEdit(); //So now on a cancel the child will be removed from the parent because the edit level is set at 1

I believe the best way to fix this is always add a clone of the object to the deleted list, so the editLevel added value will always be stay the same, even if the object is added to a different collection

Parent parent = Parent.NewParent();
Child child = Child.NewChild();
parent.Add(child);
parent.BeginEdit();
parent.Remove(child); // Clone the object and set the clone to the deleted list in the RemoveItem() method
//User decides they want to add it to a different parent
Parent parent2 = Parent.NewParent();
parent2.Add(child);  // At this step, the editLevelAdded value of the child gets incremented from 0 to 1 because we have nothing in the deleted list to compare it to
//This updates the value in the deleted list of the parent
parent.CancelEdit(); //So now on a cancel the child will be removed from parent2 because the edit level is set at 1, but the clone of parent is uneffected, and it is added back to the list.  If the object is removed form parent 2 and added back to parent, the Add method will need to updated the editLevelAdded value of the item, to whatever the clone has.

 

Are there any other issues you can think of with this solution?

 

RockfordLhotka replied on Tuesday, May 27, 2008

On further thought, this is a complex problem.

On the surface it would seem that the problem could be resolved as you suggest, or perhaps better by doing the check in the InsertItem() override (so all types of insertion/add are caught).

But the problem then is that the child object is still marked as deleted, so its IsDeleted==true.

I suppose one possibility is to detect that you are trying to re-add that item and then undelete the object, rather than actually re-adding it.

But really this whole scenario is not supported. To "undelete" a child you are supposed to call CancelEdit() on the parent (the list), thus restoring the child and list to its previous state.

So I think I will not address this in CSLA itself. However, there's nothing stopping you from overriding Add() as you suggest in your own custom base class. Or doing something similar in InsertItem() as long as you call base.InsertItem() when you are done.

But you can't do the undelete approach, because UndeleteChild() is private. So you'll have to come up with some scheme of your own by which you can mark the child object so IsDeleted==false.

dlabar replied on Thursday, May 29, 2008

So if I have a list of 500 objects, I mark one for deletion, and edit 400 other ones, in order to undelete the item that I deleted I need to call Cancel edit and redo the other 400 changes?  Granted you're not usually going to be doing this sort of thing, but it is an issue if your children can be represented in a treeview, and the user has the option of checking(adding a child object) and unchecking(removing a child object).  Maybe I should abandon trying to always keep the BO in sync with the tree and only read the tree when they call save?  This would resolve any issue with deleting and then reading the same object.

Oh and, since when have you been afraid to use refelection to call a private method?  :)

tmg4340 replied on Thursday, May 29, 2008

I may be wrong on this, but I don't think it's a fear of using reflection to call a private method.  It's the concept that the method was marked as "private" for a reason - it contains behavior that is not defined outside the scope of the class.  Using reflection to call it is, in essence, an unsupported behavior - even though, in this instance, invoking that behavior is not likely to cause any serious side effects.

As for your issue, I might take a different approach.  I would create a separate property and bind to that in your treeview - maybe something like "IsRemoved".  Yes, you would still need to check each BO and delete them on a Save based on this property value, but then you could still keep your BO's in sync with your UI and not worry about the delete behaviors.

HTH

- Scott

dlabar replied on Thursday, May 29, 2008

That's actually not a bad idea.  If I hadn't already implemented the other method, I probably would have used that one.  The only issue is I would have to add it to every class that I wanted to use it for and overwrite the List Update/Save implementation. 

I just created extension methods to do what I suggested to Rocky to add.

Copyright (c) Marimer LLC