Undo Delete from collection during Child_DeleteSelf (server invoke)

Undo Delete from collection during Child_DeleteSelf (server invoke)

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


AaronH posted on Friday, July 27, 2012

I have an object graph that contains a parent, children, and grandchildren.

We are using Silverlight as the client, and we have a screen that supports editing the entire graph at the same time.  So invoking Save() on the parent will save all of its grand/children.

The specific scenario that we are plagued with occurs when a user deletes a child or grandchild, and that delete fails on the server for whatever reason (typically due to concurrency, etc.)

Typically, this scenario leaves the entire object in an invalid state, as the Save() will always fail due to that deleted object failing on Child_DeleteSelf().

This can be very frustrating for a user, as they may have made changes to the parent and children as well, and this scenario basically forces them to reload the entire graph.

So my question is: is there a way to undelete the item on the server so that I can get the object graph back into a valid state?  I've tried this in ways, and always get an exception about the collection changing.  Is there a sneaky way around this?  Admittedly, I have not dug real deep into this.

Depending on the feedback that I get, I'll follow up with further questions.

Thanks much!

JonnyBee replied on Friday, July 27, 2012

No, you cannot do this within Child_DeleteSelf.

You can however do this within the the update method but that also means that you cannot use the helper methods in CSLA like DataPortal.UpdateChildren.

Altho' I am unsure as to how the user dialog would be if this is partially successful save....

AaronH replied on Friday, July 27, 2012

"You can however do this within the the update method"

Which update method are you speaking of?  The DP_Update() of the root object?

 

Thanks

 

JonnyBee replied on Friday, July 27, 2012

You must implement your own Child_Update method on the list:

This is the default implementation in BusinessListBase:

    protected virtual void Child_Update(params object[] parameters)
    {
      var oldRLCE = this.RaiseListChangedEvents;
      this.RaiseListChangedEvents = false;
      try
      {
        foreach (var child in DeletedList)
          DataPortal.UpdateChild(child, parameters);
        DeletedList.Clear();
 
        foreach (var child in this)
          if (child.IsDirty) DataPortal.UpdateChild(child, parameters);
      }
      finally
      {
        this.RaiseListChangedEvents = oldRLCE;
      }
    }

. You need to override this method and implement your own to "re-add" the item if delete failed.

 

 

AaronH replied on Friday, July 27, 2012

I see, that would work, but not the most ideal.


What if I allowed the delete to complete (catch the exception and leave the item removed from the collection) but then want to notify the user that the delete was unsuccessful and will reappear when the object is reloaded?  Not ideal, but this way all the DP methods would succeed.

I thought about adding a ConcurrencyError property to the deleted object, but of course, the object won't exist after the object returns to the UI.

I thought about traversing upwards until I grab it's parent and adding the message there, but that's pretty hacky.

I thought about writing it out to the ClientContext or GlobalContext, but it seems that values written to them on the server are not persisted back to the client (only the originating values that came from the client are).

Any thoughts on how I could marshal messages back with the payload?

 

Thanks again!

JonnyBee replied on Friday, July 27, 2012

GlobalContext is 2-way so that may be used but I would prefer an separate AppContextObject and add the messages to the root object as it´s own property.  It is a bit tricky when you do async DataPortal calls so I would prefer to make it a property on the root object.

You can override the DataPortal_BeginInvoke and DataPortal_EndInvoke on the root object and set the property value in EndInvoke from a "context" variable.

AaronH replied on Friday, July 27, 2012

Yes, this is actually the route that I have chosen.  Not happy with it, but I essentially add concurrency error messages to the GlobalContext, and on DataPortal_OnDataPortalInvokeComplete (which only invokes for the root object), take all of the messages and add them to a ConcurrenyErrors list (defined in my own base class that inherits from businessBase).  Still a bit hacky, but it works.

The problem I have now is with Root business lists, as they don't honor serializing properties.

I know I'm supposed to override the OnGet/Set methods, but they don't seem to be honoring serializing my property back to the client.

I have defined a property of type MobileList<string> to hold all of the concurrency errors, and then de/serializing via the OnGet/Set/Children methods, but to no avail.

Any thoughts on that?

JonnyBee replied on Saturday, July 28, 2012

Yes, you cannot use all types of root objects with this technique.

I would change the root list objects to be child objects of an EditableRoot object.

Have you thought of maybe using a CommandObject to wrap the save method (somewhat like the CQRS type of code)?

 

AaronH replied on Monday, July 30, 2012

That's a great idea, thanks!

Copyright (c) Marimer LLC