CancelEdit() bug when called on a clone of a business object.

CancelEdit() bug when called on a clone of a business object.

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


Jaans posted on Thursday, January 10, 2013

Came a cross some unexpected behaviour from the CancelEdit() method that was frustrating to figure out.

I'll start at the beginning. My first issue was that CancelEdit on a large object graph under Silverlight (that is bound to many UI elements), is very slow, but more importantly it blocks the UI thread until CancelEdit completes. For our scenario the 2-3 second delays are yielding an unacceptable bad user experience.

Possible feature request: Provide support for calling CancelEditAsync() or implementing it so that it doesn't block??

So during my investigation, I found that all the various Silverlight controls and other dependency properties that are bound to the CSLA business object, is contributing to the slow behaviour.

One possible solution to the blocking of the UI, was to first create a Clone() of the business object, then call CancelEdit() on that clone. Once done, I then replace the Model property on my ViewModel with the cloned instance (which will have all the dependencies rebind to the new Model instance).

Success! The user experience is substantially better, until I discovered that the CancelEdit() behaved differently on the clone than on the original instance. Specifically I've lost data. I quadruple-checked and found that if I call CancelEdit() on the original object without cloning it first, data is not lost. However if I clone it first, then I lose data.

So what do I mean with lose data? In my case I have a freshly fetched business object that is not dirty. The object is an editable root that includes a child property composing an editable child collection of editable children. 
What I mean by data lost is that this editable child collection is suddenly empty where previously there were children (that weren't dirty either). Calling CancelEdit() on the original instance does not clear this collection.

(Though I'm not sure it's relevant, this children have grand children, etc. and there are other child properties on the root too.)

Any ideas?

Jaans

Jaans replied on Saturday, January 12, 2013

Hi Rocky / Jonny

I've narrowed this down to be a bug specific the Silverlight CSLA code.

To help identify and repro the issue, I've created a contrived sample application showing that it works correctly in the .NET environment, but not in the Silverlight environment.

You can download this sample project from my SkyDrive folder here: 
https://skydrive.live.com/redir?resid=DFDE570BFD562200!1623&authkey=!ANfe3aGtIcEBsZo

It's a super simple VS2012 solution with .NET 4.5 / SL5 projects that references CSLA 4.5.10

Please let me know if you need any assistance in getting it going.

Thanks,
Jaans

RockfordLhotka replied on Saturday, April 06, 2013

Do you have a specific suggestion for a fix? Something you can post into the github issue and/or just fix and submit a pull request?

https://github.com/MarimerLLC/csla/issues/11

Jaans replied on Saturday, April 06, 2013

Unfortunately, I haven't been able to figure out *why* it does that in the Silverlight code, only that it does and that it does not happen in the .NET code.

I was hoping that the simple repro would be enough for someone to use and figure out why.

JonnyBee replied on Saturday, April 06, 2013

Hi,

I suspect this is caused by SL using the MobileFormatter to serialize/deserialize the clone.

This is the code from Core.UndoableBase:

 

    // keep a stack of object state values.
    [NotUndoable]
    private Stack<byte[]> _stateStack = new Stack<byte[]>();
    [NotUndoable]
    private bool _bindingEdit;
    /// <summary>
    /// Override this method to insert your field values
    /// into the MobileFormatter serialzation stream.
    /// </summary>
    /// <param name="info">
    /// Object containing the data to serialize.
    /// </param>
    /// <param name="mode">
    /// The StateMode indicating why this method was invoked.
    /// </param>
    protected override void OnGetState(SerializationInfo info, StateMode mode)     {       info.AddValue("_bindingEdit", _bindingEdit);       base.OnGetState(info, mode);     }     /// <summary>     /// Override this method to retrieve your field values     /// from the MobileFormatter serialzation stream.     /// </summary>     /// <param name="info">     /// Object containing the data to serialize.     /// </param>     /// <param name="mode">     /// The StateMode indicating why this method was invoked.     /// </param>     protected override void OnSetState(SerializationInfo info, StateMode mode)     {       _stateStack.Clear();       _bindingEdit = info.GetValue<bool>("_bindingEdit");       base.OnSetState(info, mode);     }

Notice that _stateStack is not added in OnSetState and actually cleared in OnSetState so there is no _stateStack to revert to in CancelEdit when MobileFormatter is used to Clone the object structure. I assume the .NET code use one of the builtin binary formatters that will serialize _stateStack as the field is not mark as .NonSerialized so that it works in .NET.

RockfordLhotka replied on Sunday, April 07, 2013

fwiw, a change was just made to ViewModelBase to suppress UI events during a cancel operation. This was done as a performance improvement, and might help you in this case - if you are using a viewmodel to manage your model.

Jaans replied on Sunday, April 07, 2013

Interesting indeed.

So would a fix for the missing children on the cancel edit then be to just include the _stateStack in the serialisation (OnSetState / OnGetState)?

Dakianth replied on Monday, December 16, 2013

This problem is not limited only to the method of CancelEdit. All operations that involve undo or clone are having the same problem in silverlight.

I started a thread about it but still have time for it.

http://forums.lhotka.net/forums/p/11990/55559.aspx # 55559

Copyright (c) Marimer LLC