Can't seem to be able to undo changes after exception thrown by DataPortal

Can't seem to be able to undo changes after exception thrown by DataPortal

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


lukky posted on Wednesday, January 07, 2009

Hi,

I'm doing some testing in my app, and I'm causing the DataPortal to throw an exception, and thus the BO is not saved to the database. But then, even if I Cancel my changes, the UI still shows the modified values.

It's a WinForm application, with CSLA 3.6, and I use BindingSourceHelper/BindingSourceNode along with BindingSource to data bind my controls to the BO.

So, when the user clicks the Save button, I do the following:

Try
    _bindingNode.Apply()
    _curentBO = _curentBO.Save()
Catch
    'MsgBox to user
Finally
    _bindingNode.Bind(_curentBO)
End Try

Besides that, I also have this code when the user clicks the Cancel button:

    _bindingNode.Cancel(_curentBO)

Which is more or less what Rocky suggests in his book on page 308.

Unfortunately, as I mentioned, if there is indeed an exception thrown during the Save operation, and that the user then clicks Cancel, the UI still shows the edited values, and not the original values.

So, I've looked at the code in BindingSourceNode.Apply(), and this calls EndEdit() on the BindingSource, effectively putting an end to the Undo functionality provided by the BindingSource.

How should this scenario be handled ?

Thank you.

lukky replied on Thursday, January 08, 2009

As a follow-up on this, it seems that the design of CSLA is that the object must be at EditLevel = 0 in order to be savable. Correct me if I got this wrong.

In my current scenario, the forms have an "Edit" button that enables the controls for edit. The controls are otherwise in a disabled state. There is also a Cancel button that reverts the changes to the BO, and disables the controls. Finally, there is a Save button which also reverts the controls to a disabled state, but only if the Save operation succeeds.

So, when the user wants to edit the BO, he click edit, the controls enable him to do so, then when he's finished, he clicks Save. If the database throws an exception, I then inform the user about it, but there's no way for him to undo the current changes, as they have been applied just before the call to BO.Save(). The situation for the user now is that his BO in unsaved, he is still in "edit" mode, but he can't cancel his changes.

As a possible workaround this, and this is where I'd like input from experienced users, I was thinking of Cloning the object prior to calling ApplyEdit/Save so that if the save throws, I rebind the UI to the clone and let user decides what he wants to do with it.

Will cloning the object throw me down the pit at some point ?

Thank you.


ajj3085 replied on Thursday, January 08, 2009

I think doing the clone before calling EndEdit and saving is the recommended way to go.  I think this may have been posted about before, so you may find a better answer if you search.

lukky replied on Thursday, January 08, 2009

Hi,

Unfortunately, implementing Clone before Save leads to another problem with WinForms DataBinding.

From what I can observe, when I Clone the original BO, its EditLevel is 2. In case of exception during Save, I rebind this object using BindingSourceNode. This causes the EditLevel to go to 3. When I try to Save again, I get an error because the EditLevel is not at 0, and thus the object can't be saved.

Now, as to why the EditLevel = 2 before the first Save, it seems that BindingSourceNode.Bind() raises the EditLevel twice. Once by calling ISupportUndo.BeginEdit(), and again indirectly by passing the BO as the DataSource to the associated BindingSource.

It seems to me that this is somehow flawed (at least from my point of view, feel free to readjust me). What I think should happen is that if the BO is already at EditLevel > 0, then we should not call ISupportUndo.BeginEdit(), but only pass the BO to the BindingSource.

I'd really like to get to the bottom of this. Rocky, please help !! ;-)

Regards,

rsbaker0 replied on Thursday, January 08, 2009

If you unbind before you Clone(), shouldn't the EditLevel be at 1?

lukky replied on Thursday, January 08, 2009

Good point.

Actually, I don't explicitly unbind, I use the BindingSourceNode.Apply() after Clone(), before Save().

Maybe in my scenario I should not rely on the BindingSourceNode helper class and bind/unbind my BOs manually ?

One thing that seems to be a recurring pattern on this forum is the "bad" behavior of the BindingSource component with regards to when/how many times it calls BeginEdit on the BO. Is there anyone on this planet that knows exactly what/when/why it does what it does ?  Crying [:'(]

So, I guess you're suggesting to go the manual handling of bind/unbind instead of using the BindingSourceNode ?

Best regards.

rsbaker0 replied on Thursday, January 08, 2009

I haven't specifically worked with the BindingSourceNode helper class, but I've seen "unbind before you save or undo and then rebind" here over and over and over again.

(I've chosen to disable the IEditableObject interface and do my own BeginEdit/ApplyEdit calls because the WinForms binding behavior seems so flaky and I ran into what was basically a show stopper. )

lukky replied on Thursday, January 08, 2009

You are certainly right that Windows Forms DataBinding can be a royal pain in the lower rear end.

In view of this, and since you seem to have developed a custom way of dealing with it, I have a few questions if you don't mind.

When you disable the IEditableObject interface on your BO, do you still use the BindingSource to "centralize" Controls binding, or do you bind directly to the BOs ?

What pitfalls have you gotten into because of this ? I ask because Rocky warns us against it specifically in his book (P. 292, middle of page, Caution note).

On a side note, I'm a bit puzzled to be faced with those challenges and that only a few people on this forum seem to be faced with them as well (or it would seem so). I really think that what I'm doing is very basic data entry form stuff, and I fail to understand why I keep banging my head against a brick wall with such a "basic" scenario.

So far, CSLA has behaved very well in the "business logic" aspects. I can only assume that the issues I'm facing are specific to the way Windows Forms DataBinding is done, and that once I finally move to WPF, this kind of "basic" scenario will be handled better.

Many thanks.

rsbaker0 replied on Thursday, January 08, 2009

Yes, I'm still using BindingSources. Nothing has really changed in that regards except that the CSLA BO's ignore the IEditableObject calls made by via the bindings.

Our application has basically two editing paradigms -- there are EditableRootLists everywhere, side by side with a multi-tabbed property page that corresponds with the current object in the grid. Occasionally, you may want to bring a stand-alone property page or similar also. There can be almost arbitrarily many such screens open at once -- in fact I'm lobbying (but still losing the argument) to convert into more of a "browser" style interface where you navigate forward and backward like you would in an Internet Browser, but I digress. The whole thing is built with individual controls rather than forms, so eventually I'll win the argument and we'll switch to the browser style, or maybe the user could even have the option. :)

The show stopper I ran into was that with an EditableRootList, the grid immediately calls BeginEdit(), so BindingEdit is almost alway true. CSLA doesn't cascade these through the object graph when they come from IEditableObject, so I found I could not undo changes on any child objects.

So, I have an editing "manager" or supervisor classes that manages these two types of scenarios. For a grid, the supervisor hooks a few events so I know when the current changes, and appropriately Save() on the previous and BeginEdit() on the new current, etc.

One interesting thing that happened was that because now my BeginEdit()/ApplyEdit() calls are cascaded through the entire graph, all sorts of hidden EditLevel mismatch bugs that were lurking there all along were suddenly revealed. They were easy to fix once I knew they were there.

So far it seems to be working OK, but I suspect a few tweaks will be needed as we go forward.

Incidentally, use of managed properties for child objects and lists will go a long way toward fixing many edit level issues, especially if you make extensive use of  "lazy loading" like we do.

lukky replied on Thursday, January 08, 2009

If I read you correctly, it means that you become responsible for calling BeginEdit/EndEdit/CancelEdit on the BOs in your UI code. That also means that I don't need to unbind the BO from the BindingSource before saving. Is that correct ?

I think this would apply well to our way of editing the BOs, in that we have buttons to put the UI in edit mode, cancel the changes and save the BOs. I'll do some tests and see how it behaves.

I think one thing that goes for me is that I don't use editable DataGridViews, so that's one less thing to worry about.

I'd really like to understand the BindingSource component better though.

Thanks for the info.

rsbaker0 replied on Thursday, January 08, 2009

Well, in theory you don't have to unbind if you've turned off IEditableObject, but in practice CSLA doesn't "Save" your object in place -- it saves a copy and returns the copy, so you need to rebind to the returned copy anyway. So, I have left the unbind/rebind code in place where I was using. The EditableRootList replaces the item in the list with the saved copy automatically.

BindingSources are cool (at least in WinForms) -- you can associate one with either a list or a single object, and the same UI can in theory deal with either one. They also decouple the UI from the actual objects being bound to -- the individual binding (association between property in the datasource and the control) doesn't care as long as whatever is in the binding source has a property of the same name and compatible type.

Copyright (c) Marimer LLC