Edit Level Mismatch - Single Child Object

Edit Level Mismatch - Single Child Object

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


Tom_W posted on Thursday, June 04, 2009

Hopefully this is me being stupid...

I have an EditableRoot Contact object that has a single child ProfileCustomer (EditableChild) object.

I have databound both objects onto a ContactEdit form and this all populates nicely.  However, when I call mContact.CancelEdit I get a 'Edit level mismatch in UndoChanges' message from UndoableBase.

The code I'm calling to cancel the edit is:
            UnbindBindingSource(Me.ContactBindingSource, saveObject, True)
            mContact.CancelEdit()

Debugging through shows the issue, the EditLevel is getting decreased on the Contact but not on the child ProfileCustomer.

I'm guessing this is a databinding issue, anyone experienced this when using an aggregated child object?

Tom_W replied on Friday, June 05, 2009

Sorry, should have also said that I did the databinding by dragging both the Contact fields and the child ProfileCustomer fields onto the form from the Contact datasource.  I.e. there is only one bindingsource.  Is that OK?

I tried adding a ProfileCustomerBindingSource and setting it's data souce to the ContactBindingSource and its data member to to the ProfileCustomer property, but this also caused an Edit Level Mismatch - although I think that was actually for a different reason (in this case it seemed like begin edit was getting called on the child ProfileCustomer as part of the action of cancelling the parent's edit).

Which is the right way to deal with single child objects - or should it not matter?

ajj3085 replied on Friday, June 05, 2009

Well, if possible, I'd try the facada pattern; don't expose the child object directly, add properties onto the root object and have those properties delegate to the child object.

Tom_W replied on Friday, June 05, 2009

Hi Andy

I was waiting for someone to say that!   There's a reason for this approach unfortunately, the use case here is that there's a main contact edit form (ContactEdit) that then has 'plug-in' customer specific profile classes.. so the idea is that the ContactEdit form doesn't know the exact details of the the ProfileCustomer and hence a facade would be less appropriate I think?

Anyway, before we trigger another OO design thread (Devil [6]), regardless of whether the above design is good or bad, there must be some scenarios where there would be a one-to-zero-or-one relationship between a Root and Child class and it is desirable to edit both the Root and Child objects on the same form. 

Someone must have done this I'm sure, all I really need to know is that it's possible?

I guess my specific questions would be:

Tom_W replied on Friday, June 05, 2009

Well, it's 10pm and I still can't figure it out!  Clearly the answer is to post a lot of code in the hope that one of the brighter minds can assist...

So, this form is just a reworking of the VB version of ProjectTracker's ProjectEdit form, with the key difference that the child object is a single object (ProfileCustomer) rather than a collection (Resources).

The key bits of the code are:



Imports Vision.BLL

Public Class ContactEdit

    Private WithEvents mContact As Contact

    Public ReadOnly Property Contact() As Contact
        Get
            Return mContact
        End Get
    End Property

    Public Sub New(ByVal Contact As Contact)

        InitializeComponent()

        mContact = Contact

        BindUI()

        ApplyAuthorizationRules()

    End Sub

    Private Sub ApplyAuthorizationRules()

        Dim canEdit As Boolean = _
        True
        'Vision.BLL.Contact.CanEditObject

        If Not canEdit Then _
          RebindUI(False, True)

        ' have the controls enable/disable/etc
        Me.ReadWriteAuthorization1.ResetControlAuthorization()

        ' enable/disable appropriate buttons
        Me.OKButton.Enabled = canEdit
        Me.ApplyButton.Enabled = canEdit
        Me.Cancel_Button.Enabled = canEdit
        'Me.AssignButton.Enabled = canEdit
        'Me.UnassignButton.Enabled = canEdit

        ' enable/disable role column in grid
        'Me.ResourcesDataGridView.Columns(2).ReadOnly = Not canEdit

    End Sub

    Private Sub OKButton_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles OKButton.Click

    End Sub

    Private Sub ApplyButton_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles ApplyButton.Click

        Using busy As New StatusBusy("Saving...")
            RebindUI(True, True)
        End Using

    End Sub

    Private Sub Cancel_Button_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Cancel_Button.Click

        RebindUI(False, True)

    End Sub

    Private Sub CloseButton_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles CloseButton.Click

        RebindUI(False, False)
        Me.Close()

    End Sub

    Private Sub BindUI()

        mContact.BeginEdit()
        Me.ContactBindingSource.DataSource = mContact

        If mContact.IsCustomer Then
            Me.ProfileCustomerBindingSource.DataSource = mContact.ProfileCustomer
        End If

    End Sub

    Private Sub RebindUI(ByVal saveObject As Boolean, ByVal rebind As Boolean)

        If mContact.IsCustomer Then
            Me.ProfileCustomerBindingSource.RaiseListChangedEvents = False
        End If

        ' disable events
        Me.ContactBindingSource.RaiseListChangedEvents = False

        Try
            ' unbind the UI
            If mContact.IsCustomer Then
                UnbindBindingSource1(Me.ProfileCustomerBindingSource, saveObject, False)
            End If

            UnbindBindingSource1(Me.ContactBindingSource, saveObject, True)

            If mContact.IsCustomer Then
                Me.ProfileCustomerBindingSource.DataSource = mContact.ProfileCustomer
            End If


            ' save or cancel changes
            If saveObject Then
                mContact.ApplyEdit()
                Try
                    mContact = mContact.Save()

                Catch ex As Csla.DataPortalException
                    MessageBox.Show(ex.BusinessException.ToString(), _
                      "Error saving", MessageBoxButtons.OK, _
                      MessageBoxIcon.Exclamation)

                Catch ex As Exception
                    MessageBox.Show(ex.ToString(), _
                      "Error Saving", MessageBoxButtons.OK, _
                      MessageBoxIcon.Exclamation)
                End Try

            Else
                mContact.CancelEdit()
            End If

        Finally
            ' rebind UI if requested
            If rebind Then
                BindUI()
            End If

            ' restore events
            Me.ContactBindingSource.RaiseListChangedEvents = True

            If mContact.IsCustomer Then
                Me.ProfileCustomerBindingSource.RaiseListChangedEvents = True
            End If

            If rebind Then
                ' refresh the UI if rebinding
                Me.ContactBindingSource.ResetBindings(False)

                If mContact.IsCustomer Then
                    Me.ProfileCustomerBindingSource.ResetBindings(False)
                End If

            End If
        End Try

    End Sub


    Protected Sub UnbindBindingSource1( _
  ByVal source As BindingSource, ByVal apply As Boolean, ByVal isRoot As Boolean)

        Dim current As System.ComponentModel.IEditableObject = _
                TryCast(source.Current, System.ComponentModel.IEditableObject)
        If isRoot Then
            source.DataSource = Nothing
        End If
        If current IsNot Nothing Then
            If apply Then
                current.EndEdit()
            Else
                current.CancelEdit()
            End If
        End If

    End Sub

    Private Sub ContactBindingSource_CurrentItemChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)

        Me.OKButton.Enabled = mContact.IsSavable

    End Sub

End Class



The errors I get are..

If I open the edit form and then just press the Cancel or Close button on the form I get:

Csla.Core.UndoException occurred
  Message="Edit level mismatch in UndoChanges"
  Source="Csla"
  StackTrace:
       at Csla.Core.UndoableBase.UndoChanges(Int32 parentEditLevel) in D:\Work\Visual Studio 2008\Projects\CSLA362\cslacs\Csla\Core\UndoableBase.cs:line 203
  InnerException:


Debugging through shows that the parent Contact object's edit level is 0, but the child Profile object's edit level is still 2.

Any help very gratefully received!

xAvailx replied on Monday, June 08, 2009

>> I.e. there is only one bindingsource. Is that OK?
Try using two binding sources, then set the datamember. Also, can you post the code for the child business object property. You can also try using the debugger and watch the edit levels when databinding, applying edit, etc...

Tom_W replied on Wednesday, June 10, 2009

Hi Avail

Many thanks for your response.  I tried the dual binding sources approach, and set the datamember of the second binding source to the ProfileCustomer property of the Contact object referenced by the first bindingsource.  Unfortunately that didn't work.

The child object ProfileCustomer property in the contact object looks like this:

        #region One to Manys unhandled by code gen

        private static PropertyInfo<ProfileCustomer> ProfileCustomerProperty = RegisterProperty(new PropertyInfo<ProfileCustomer>("ProfileCustomer", "ProfileCustomer"));

        /// <summary>
        /// Returns the ProfileCustomer for this contact.
        ///
        /// NB: This field should not be lazy instantiated as
        /// not all contacts will have this profile.  To create
        /// a profile use the relevant AddProfileXXX method.
        /// </summary>
        public ProfileCustomer ProfileCustomer
        {
            get {return GetProperty(ProfileCustomerProperty);  }
            set { SetProperty(ProfileCustomerProperty, value); }
        }

        /// <summary>
        /// Adds a customer profile to this contact.
        /// </summary>
        /// <returns></returns>
        public ProfileCustomer AddProfileCustomer()
        {
            if (FieldManager.FieldExists(ProfileCustomerProperty))
            {
                LoadProperty<ProfileCustomer>(ProfileCustomerProperty, ProfileCustomer.NewProfileCustomer());
            }
            else
            {
                //todo:  Should we throw an error here if the user tries to add a customer profile
                //      to a contact that already has a customer profile?
            }
            return ProfileCustomer;
        }


        #endregion //one to manys



Any other thoughts?  They would be very gratefully received, I'm at my wits end with it!

xAvailx replied on Wednesday, June 10, 2009

In what scenario do you get the mismatch error? when adding a new contact? You may want to put in your watch window the edit levels so you can monitor when they get added/removed, that may help you debug.

I have a similar scenario in which I am binding to a "child" property but I have 6 different child subtypes and I was able to get it working using Winforms, CSLA 3.6.2, .Net 3.5.

I did end up using the BindingSourceNode class, but that was mostly out of convenience so didn't have to manually bind/unbind 6 different bindingsources :)

Tom_W replied on Saturday, June 13, 2009

Thanks for the reply and for all your help.  I've switched over to the bindingsourcenode (using the code from the book as a basis) and that's sorted the problem above - weeks of tearing hair out resolved :-). 

All the edit levels seem to be behaving correctly now except when I call Cancel then Apply, which is causing an 'Object is still being edited and cannot be saved' error.  I will dig into the code and try and work out why this is happening.

simon replied on Friday, November 20, 2009

I have the same problem.

any progress? can you shared with me?

simon replied on Friday, November 20, 2009

anybody have a solution?

I am unhinged about this.

Tom_W replied on Monday, November 23, 2009

Hi Simon

In answer to your question:  In truth I'm not sure exactly what we did to fix the problem (I'm assuming you are having an issue with the Cancel then Apply scenario?) or even if we actually have fixed it.  Our UI design now uses the ribbon toolbar, so there is no longer an explicit Cancel button on our forms and we aren't calling BindingTree.Cancel anywhere in code, only BindingTree.Close.

Sorry not to be of more help.



Copyright (c) Marimer LLC