Tied in knots with Data Binding!

Tied in knots with Data Binding!

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


razorkai posted on Friday, August 25, 2006

Ok, I've wasted so much time on this now I'm getting desperate.  I have an object that contains a collection of child objects.  I allow editing of a child object through a modal form to which I pass the parent object and child when the form is created.  I have a Save button with code like this:-

try
{
   ChildBindingSource.EndEdit(); 
   ParentObj.Save();  
}
catch
{   
   //display exception in some way
}   

I also have Cancel button that calls ChildBindingSource.CancelEdit.

This all works fine if the Save succeeds or the user clicks Cancel.  However, if the Save fails due some problem outside of Csla business rules the problems begin.  First off the EndEdit call succeeds as the object is Valid.  Then the Save fails, the exception is caught and displayed.  At this point the user could click cancel, but then the ChildBindingSource.CancelEdit code beneath the cancel button will actually do nothing because the EndEdit has lowered the EditLevel of the object back to zero.  This means the object is still dirty when the modal form is closed and it contains changes that have not been committed to the DB - not great!

The book suggested doing things like this

ParentObject temp = ParentObject.Clone();
temp.ApplyEdit();
ParentObj = temp.Save();  

but then Rocky said that you should not use ApplyEdit but call EndEdit on the binding sources instead in this thread...

http://forums.lhotka.net/forums/thread/98.aspx

If you do that then how do you use the Clone technique to ensure the object remains in the correct state until the Save successfully completes?

Please help I am pulling my hair out!

Thanks

Brian Criswell replied on Friday, August 25, 2006

Maybe this would work?

ChildBindingSource.EndEdit();
ParentObj = ParentObj.Clone().Save(); 

razorkai replied on Friday, August 25, 2006

Hi Brian

This does not solve the problem of the EndEdit succeeding but the save failing.  As soon as the EndEdit call occurs the object cannot be reverted back to its pre-edit state without fetching the object from the DB again.

matt tag replied on Friday, August 25, 2006

I think your problem is one of design.  You usually don't have a collection of root objects that you call Save on one at a time (at least, I don't usually work in this way).

Usually, I have a "thin" readonly collection to display objects in a list, and when the user doubleclicks to edit one of them, I create a "fatter" BusinessBase to do the editing.

If you're set up in this way, then when the user hits "Cancel", the BusinessBase object just goes away.

The downside to this setup is that you now often have to reflect changes from the BusinessBase back to the readonly collection. The brute force way is to reload the collection, but there are other ways to set up this communication (ActiveObjects is one such way).

matt tag

ajj3085 replied on Friday, August 25, 2006

I have to agree with matt, unless you're using the new editablerootcollection (I think that's what its called).

At any rate you should be calling end or cancel edit on the parent binding source, not the child.

HTH
Andy

razorkai replied on Friday, August 25, 2006

Hi Andy

Just to reiterate, the collection contains child objects not root ones.  Even if I call end or cancel edit on the parent binding source it does not resolve the issue that if the subsequent save fails the object cannot be reverted to its pre-save state as no cloning has been done.  Hope I'm being clear here, its kind of tricky to get across :)

ajj3085 replied on Friday, August 25, 2006

Ok now I got it, sorry.

I would think you just call endedit on the binding source, than clone, then save.  If the save fails, the object remains as the used modified it, perhaps allowing them to try again or something, depending on what you need to do.

Now if you want the object to revert to its state BEFORE the user made any changes, you'll have to make a clone when your form loads and keep the clone around.  In your catch statement, you can set the original to a clone of the clone (otherwise they'll start editing the clone, and if you need to revert again you won't be able to).

Does that solve your problem?

Andy

razorkai replied on Friday, August 25, 2006

We're getting closer, but no cigar yet I'm afraid!  If you do what you suggest the object does NOT remain as the user edited it because the edit level of the object is set to zero by the call to EndEdit.  This means the user is left with a Dirty object that they cannot save due to some external problem (in this case it is a stored procedure error preventing the save), and they cannot cancel their changes because CancelEdit only works with an edit level above zero! 

ajj3085 replied on Friday, August 25, 2006

How does cloning the object and keeping the clone around before you throw the original into the bindingsource not solve your problem? 

What exactly do you want to have happen in this case?  Do you want the form to revert back to the state it was before they started editing?  Is the error something the user can correct, and thus they should be able to continue editing?  Cloning the object as the form opens will solve this, because you have a seperate copy which is never edited by the user. 

razorkai replied on Friday, August 25, 2006

In this case the user cannot fix the error, so I guess the solution is to cancel their changes.  Like you say, this can be achieved by cloning the object before the user edits the object and then reverting if they hit an unrecoverable error.  So thsi will solve my problem :)

However, I am still confused as to the best way to do things with regard to using the binding source EndEdit method and the object's AcceptChanges method.  If you use AcceptChanges (which Rocky suggests you shouldn't) then you can use the clone method to keep the object in the correct state if AcceptChanges fails.  If you use the binding source EndEdit method like Rocky says we should, then you can't use the clone method of the object to deal with EndEdit exceptions. Phew!  Hope your following me.

 

[Edit]

Oops.

Just noticed when I posted this earlier I meant to say ApplyEdit not AcceptChanges (which is in fact called by ApplyEdit).  Seems like I was understood anyway, but tthought I'd better clarify it ;).

ajj3085 replied on Friday, August 25, 2006

razorkai:
However, I am still confused as to the best way to do things with regard to using the binding source EndEdit method and the object's AcceptChanges method.  If you use AcceptChanges (which Rocky suggests you shouldn't) then you can use the clone method to keep the object in the correct state if AcceptChanges fails.  If you use the binding source EndEdit method like Rocky says we should, then you can't use the clone method of the object to deal with EndEdit exceptions. Phew!  Hope your following me.


Hmm... now that I think about it, the accept changes on the clone should be fine.  The bindingsource knows nothing of this clone you just created and attempted save on...  the problem is if you call accept or cancel on an object of which some binding source is aware.. then you have problems.

So I think accept on the clone (which is going to be saved) is fine... because if it fails, the object its discarded.  If it succeeds, you'll be then changing the binding source to this new object, which should start the begin edit again... and everything I would think should be fine.

Make sense?

If my reasoning is wrong, I hope someone will point this out to me..

Andy

xal replied on Friday, August 25, 2006

I do this in a method that returns the saved BO. This is done in a base form, and the method that calls this, then raises an event. I handle that event in the derived form in order to reassign the new object to the binding source.

        Dim Cloned As T = Nothing
        Try
            Cloned = mModel.Clone
            Cloned.ApplyEdit()
            Cloned = Cloned.Save
        Catch ex As Csla.Validation.ValidationException
             ...
            Return Nothing
        Catch ex As Exception
            ...
            Return Nothing
        End Try
        Return Cloned



Andrés

razorkai replied on Friday, August 25, 2006

Andrés

Just to clarify, the method in the base form is generic?  Something like

T SaveObject<T>()  (Sorry C# is my language of choice <g>)

Also what do you mean by "and the method that calls this"?  Which method?  Maybe you could explain in a little more detail, as it looks interesting!

 

xal replied on Friday, August 25, 2006

Well, my UI knows little about the business object. It just takes care of UI stuff, like events, and binding sources and controls. All the different logic for checking that the objects are valid, saving, and whatever, is running behind, in a generic base form.

I don't even have a save button in my form. The save button is in a toolbar, and the base form implements an interface (which i jusc called ISavable). The interface tells you whether you can save the object and also has a save method, which is implemented in my base form.

That Save() Method, calles that piece of code and if it returns the object, it calls an event that says the current object has changed. That is the event my derived form hadles to set the Datasource for the binding source. My form does never set the binding source directly from the get.

The constructor for the form takes the bo as a param, and you just call MyBase.SetObject(theBO).
I do that in order to do whatever I need to do with the object, like for example hook up the error treeview I use (which I will post in codeplex soon). After that, I raise the event so that the derived form hooks up the bo to the binding source. That sounds crazy, but the problem is that when you save, you need to refresh it, and since I'm trying to abstract the saving process, the simplest way to handle it was like that.


Andrés

razorkai replied on Saturday, August 26, 2006

I like the sound of this approach a lot!  Very different to the way I am handling things at the moment.  It certainly gives me some food for thought.  The error treeview sounds fantastic, can't wait to see that in Codeplex!

razorkai replied on Friday, August 25, 2006

The only reason I am questioning using ApplyEdit (AcceptChanges) is that the thead I linked to in my original post said you shouldn't use it!  I am going to have to do some more testing <g>.  I wish there was one definitive call to Save that did all this stuff for you!

ajj3085 replied on Friday, August 25, 2006

I think that the AccpetChanges / ApplyEdit thing only applies if the object is the data source for a binding source.  In that case, you don't want to call any of the xxxEdit methods on the object, since the binding source needs to be 'kept in the loop' so to speak.

However, calling those methods on an object NOT being used as a datasource for a binding source (such as the clone object) should be acceptable.. at least that's how I am interpereting things.

Andy

razorkai replied on Saturday, August 26, 2006

Makes sense.  I wasn't quite sure how to interpret what Rocky had said.  Thanks for bearing with me on this thread Andy!

razorkai replied on Friday, August 25, 2006

Matt

I did not say I had a collection of root objects, I have a collection of child objects.  Therefore I have to edit them as children and save them through the parent in this way.

Copyright (c) Marimer LLC