ReadOnly Parent - Writable Child; EditForm. Best plan of attack?

ReadOnly Parent - Writable Child; EditForm. Best plan of attack?

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


gajit posted on Friday, October 27, 2006

Hi guys,

I've come across another scenario that has my thinking cap on - and causing a lot of smoke...

I have a BO that contains a parent class of read-only summary information, that basically reflects totals of what is in my editable child rows.

I have constructed my business classes as I normally would, but in the   Dataportal_Update method of my parent, I omit any call to update the parent record (as it is readonly data) and move directly onto updating the children;

' Update Children;

mCUSTOMERS.Update(cn, Me)

What I essentially have is a dialog form that allows the user to select customers . The data from the parent simply presents them with a summary of what they have selected.

My problem as you can might have already figured out is that I need to return the 'refreshed' parent data back to the UI, when they select/deselect a child - essentially doing a childrows update and then refreshing the parent and children.

What would be my best approach here?  I've tried calling my Fetch() class immediately after the child update. But the UI doesn't appear to rebind correctly.

The UI SaveSelection Code is per examples;

Using busy As New StatusBusy("Saving...")

' stop the flow of events

Me.ProposalBindingSource.RaiseListChangedEvents = False

' do the save

Dim temp As Proposal = mProposal.Clone

temp.ApplyEdit()

Try

mProposal = temp.Save

mProposal.BeginEdit()

If rebind Then

' rebind the UI

Me.ProposalBindingSource.DataSource = Nothing

Me.ProposalBindingSource.DataSource = mProposal

ApplyAuthorizationRules()

End If

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)

Finally

Me.ProposalBindingSource.RaiseListChangedEvents = True

End Try

End Using

Should I be reinitiating my mProposal class in the rebind by doing something like;

mProposalBindingSource.DataSource = mylib.Proposal.GetProposal(sls)

I suspect that's neither an OO approach and/or would cause all sorts of issues.

Or is it a problem because I'm nt updating my parent class? Should I create a 'dummy' parent update?

Kind of lost on this one....  if you guys have any pointers...

 

Many thanks,

Gaj.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ajj3085 replied on Friday, October 27, 2006

First, please, not so many returns at the end of a post.

Second, after you turn on raiselistchangedevents, call ResetBindings( false) on the bindingsource.

This will cause the updates to occur.  Remember, you turn off listening for the events, they fire and are ignored.. they are not queued up until RaiseListChangedEvents is set back to true.

HTH
Andy

gajit replied on Friday, October 27, 2006

My apologies for the returns - I did try to remove them after posting, but there appears to be a problem...

Ok, this is what I have done.

In my business class, I update my children - then do what is for all intents and purposes, a Fetch. (At this point, I'm unsure how I can call the Fetch/GetProposal method without duplicating code).

Here's the Dataportal_Insert :


        Using cn As New SqlConnection(Database.crm2k6Connection)
            cn.Open()
            Using cm As SqlCommand = cn.CreateCommand
                cm.CommandText = "UpdateProposal"
                DoInsertUpdate(cm)
            End Using

            mCUSTOMERS.Update(cn, Me)

            Using cm As SqlCommand = cn.CreateCommand

                cm.CommandType = CommandType.StoredProcedure
                cm.CommandText = "GetProposal"
                cm.Parameters.AddWithValue("@SALESREP", Me.SALESREP)
                cm.Parameters.AddWithValue("@USERNAME", Csla.ApplicationContext.User.Identity.Name)

                Using dr As New SafeDataReader(cm.ExecuteReader)
                    dr.Read()
                    With dr

                        mSALESREP = .GetString("SALESREP")
                        mTOSALESREP = .GetString("TOSALESREP")
                        mACCOUNTS = .GetInt32("ACCOUNTS")
                        mORDERVALUE = .GetDecimal("ORDERVALUE")
                        mUNITS = .GetInt32("UNITS")
                        mUNITVALUE = .GetDecimal("UNITVALUE")

                        ' load CUSTOMERS
                        .NextResult()
                        mCUSTOMERS = Proposal_Customers.GetProposal_Customers(dr)

                    End With

                End Using

            End Using

        End Using


And I also have implemented the RaiseListChangedEvents in the UI code:

        Using busy As New StatusBusy("Saving...")
            ' stop the flow of events
            Me.ProposalBindingSource.RaiseListChangedEvents = False

            ' do the save
            Dim temp As Proposal = mProposal.Clone
            temp.ApplyEdit()
            Try
                mProposal = temp.Save
                mProposal.BeginEdit()
                If rebind Then
                    ' rebind the UI
                    Me.ProposalBindingSource.DataSource = Nothing
                    Me.ProposalBindingSource.RaiseListChangedEvents = True
                    Me.ProposalBindingSource.DataSource = mCardMoves_Proposal
                    ApplyAuthorizationRules()
                End If

            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)

            Finally
                Me.ProposalBindingSource.RaiseListChangedEvents = True
            End Try
        End Using

Now, the results ARE saved, and the UI refreshed.... but only on the first update. For some strange reason, if I make any subsequent changes to the children, no update is fired. In fact IsDirty=false.

I guess I have 2 questions - most importantly, why are the subsequent updates not marking the bo as dirty, - am I missing something? 


The second being, is it acceptable to update my BO by executing a fetch() inside my update() and (ok, 3 questions) is there a "better" way of doing it?

gajit replied on Friday, October 27, 2006

Ok, this may shed a little more light on the problem;

I added a line, which effectively marks the Parent as dirty (which would never normally happen as it is not updated), just before the save.

Using busy As New StatusBusy("Saving...")
            ' stop the flow of events

            mProposal.ACCOUNTS = 0
            Me.ProposalBindingSource.RaiseListChangedEvents = False

            ' do the save
            Dim temp As Proposal = mProposal.Clone
            temp.ApplyEdit()
            ...

This now works... but it's messy... and I dont understand why...
The UI gets a refresh and the correct data (parent and child) is displayed.

Any ideas?
Thanks,
Gaj.


ajj3085 replied on Friday, October 27, 2006

Are your property setter's calling PropertyHasChanged?

gajit replied on Friday, October 27, 2006

If this was missing theobject would never be marked dirty would it?

There is only one 'editable' column in my child object; "selected"
and it is readwrite;

    Public Property SELECTED() As Boolean
        Get
            CanReadProperty(True)
            Return mSELECTED
        End Get
        Set(ByVal Value As Boolean)
            CanWriteProperty(True)
            If mSELECTED <> Value Then
                mSELECTED = Value
                PropertyHasChanged()
            End If
        End Set
    End Property

All of the others are read only properties - e.g;

Public ReadOnly Property CUSTOMERNO() As String
        Get
            CanReadProperty(True)
            Return mCUSTOMERNO
        End Get
    End Property

Is very strange....


gajit replied on Friday, October 27, 2006

I just had a thought.. (which usually means others head for cover...)...

There is a lot of rebinding going on to the form's binding sources in the UI code. There is no explicit update to the child bindingsource (i am using a datagridview to update the "selected" column).

Does the rebind of the parent bindingsource do the job of rebinding the children? If not, that COULD be the problem. the child objects I see on the form are indeed not my current object, and subsequently changes are never passed back to the the bo..

Should I have something LIKE this?  (incidentally - this DOESN'T fix the problem..):-

        Using busy As New StatusBusy("Saving...")
            ' stop the flow of events

            Me.ProposalBindingSource.RaiseListChangedEvents = False
            Me.CUSTOMERSBindingSource.RaiseListChangedEvents = False

            ' do the save
            Dim temp As Proposal = mProposal.Clone
            temp.ApplyEdit()
            Try
                mProposal = temp.Save
                mProposal.BeginEdit()
                If rebind Then
                    ' rebind the UI
                    Me.ProposalBindingSource.DataSource = Nothing
                    Me.CUSTOMERSBindingSource.DataSource = Nothing

                    Me.ProposalBindingSource.RaiseListChangedEvents = True
                    Me.CUSTOMERSBindingSource.RaiseListChangedEvents = True

                    Me.ProposalBindingSource.DataSource = mProposal
                    Me.CUSTOMERSBindingSource.DataSource = mProposal.CUSTOMERS

                    ApplyAuthorizationRules()
                End If

            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)

            Finally
                Me.ProposalBindingSource.RaiseListChangedEvents = True
                Me.CUSTOMERSBindingSource.RaiseListChangedEvents = True
            End Try
        End Using

ajj3085 replied on Friday, October 27, 2006

Wait a minute.. Think I have an idea.

Is the problem that the root object is not being marked dirty?  If that's the case, you're forgetting to override IsDirty to OR in the isDirty of the collection.

Could that be the solution?

HTH
Andy

gajit replied on Friday, October 27, 2006

This is what I have in the Business Method section of my root object... looks correct to me...

 Public Overrides ReadOnly Property IsDirty() As Boolean
        Get
            Return MyBase.IsDirty OrElse mCUSTOMERS.IsDirty
        End Get
 End Property

I'm beginning to wonder if this isn't a generic problem with csla. I've put together a number of parent-child scenarios in the last few days and have had no problems - but none of them need to persist the data after the initial save. I'm wondering if something becomes disconnected once the initial isDirty is handled and the updates are perfomed.

I wonder if Rocky might have any input on this....

ajj3085 replied on Monday, October 30, 2006

Does this happen only one release builds?  If so, you might want to use the PropertyHasChanged which takes a string parameter (the name of the property).

gajit replied on Monday, October 30, 2006

Thanks for the reply ajj,

Nope - on all builds, by default I build to debug....

It's not crucial right now, as I have the workaround in place - by 'changing' a value in the root object (which is read-only in nature - although using BusinessBase) but I'd sure like to find out if this is systemic and whether I need to look out for this in the future.

Thanks,
gaj.



gajit replied on Monday, January 08, 2007

This is an old thread, but I have JUST discovered the problem - and indeed, programmer error ;)

But I thought it might be useful to post what I had done, in case anyone else does the same thing.

My child BO's were not updating unless I changed the Parent BO.

Reason being, I was checking Mybase.IsDirty in my DP_Update method instead of the Me.IsDirty...

I must have inherited that from someone... not sure where..

Now to check all my other classes...

 

Thanks,

Gaj.

BVeenstra replied on Thursday, September 27, 2007

gajit,

I wouldn't recommend checking "Me.IsDirty" as that is an override just pointing back to itself yes?  You probably need to do something like this (AllocationDetailList is my BusinessListBase of AllocationDetail child objects) in your Parent BO:

        public override bool IsValid

        {

            get

            {

                return base.IsValid && _AllocationDetailList.IsValid;

            }

        }

 

        public override bool IsDirty

        {

            get

            {

                return base.IsDirty || _AllocationDetailList.IsDirty;

            }

        }

 





As for having to "change" the Parent BO, I believe you are missing an easier solution...

You need to hook the ListChanged event of your Child BO's (BusinessListBase) like the following:

            //...

        private AllocationDetailList _AllocationDetailList = null;

            //...


        private void Fetch(SafeDataReader dr)

        {

 

            //...


            _AllocationDetailList.ListChanged += new ListChangedEventHandler(AllocationDetailList_ListChanged);

 

            MarkOld();

 

        }


            //...


        void AllocationDetailList_ListChanged(object sender, ListChangedEventArgs e)

        {

            PropertyHasChanged("--some parent property--");

        }


            //...




You need to also rehook your List's ListChanged in your OnDeserialized method so that when you call the Clone method (recommended WinForm way to Save) in the object graph that your events will all work with DataBinding (this again in your Paent BO):

        protected override void OnDeserialized(System.Runtime.Serialization.StreamingContext context)

        {

            base.OnDeserialized(context);

            //REQUIED to rehook us up to DataBinding after SAVE!

            _AllocationDetailList.ListChanged += new ListChangedEventHandler(AllocationDetailList_ListChanged);

        }


            //...


        void AllocationDetailList_ListChanged(object sender, ListChangedEventArgs e)

        {

            PropertyHasChanged("--property name in your Parent BO here--");

        }

 





gajit replied on Thursday, March 26, 2009

BVeenstra:
gajit,

I wouldn't recommend checking "Me.IsDirty" as that is an override just pointing back to itself yes?  You probably need to do something like this (AllocationDetailList is my BusinessListBase of AllocationDetail child objects) in your Parent BO:

        public override bool IsValid

        {

            get

            {

                return base.IsValid && _AllocationDetailList.IsValid;

            }

        }

 

        public override bool IsDirty

        {

            get

            {

                return base.IsDirty || _AllocationDetailList.IsDirty;

            }

        }

 





As for having to "change" the Parent BO, I believe you are missing an easier solution...

You need to hook the ListChanged event of your Child BO's (BusinessListBase) like the following:

            //...

        private AllocationDetailList _AllocationDetailList = null;

            //...


        private void Fetch(SafeDataReader dr)

        {

 

            //...


            _AllocationDetailList.ListChanged += new ListChangedEventHandler(AllocationDetailList_ListChanged);

 

            MarkOld();

 

        }


            //...


        void AllocationDetailList_ListChanged(object sender, ListChangedEventArgs e)

        {

            PropertyHasChanged("--some parent property--");

        }


            //...




You need to also rehook your List's ListChanged in your OnDeserialized method so that when you call the Clone method (recommended WinForm way to Save) in the object graph that your events will all work with DataBinding (this again in your Paent BO):

        protected override void OnDeserialized(System.Runtime.Serialization.StreamingContext context)

        {

            base.OnDeserialized(context);

            //REQUIED to rehook us up to DataBinding after SAVE!

            _AllocationDetailList.ListChanged += new ListChangedEventHandler(AllocationDetailList_ListChanged);

        }


            //...


        void AllocationDetailList_ListChanged(object sender, ListChangedEventArgs e)

        {

            PropertyHasChanged("--property name in your Parent BO here--");

        }

 


Revisiting this post as it appears to be the answer I'm lookig for....  but...

I'm trying to implement this - and am having issues.... probably because the example(s) provided are VC.Net....

I am basically having issues understanding WHERE the pieces go...

 

E.g.

The hookup handler - is placed WHERE??... in the parent BO? (businessbase)... or in the childcollection (businesslistbase)  - or in the child objects themselves? (businessbase).

The code;

private void Fetch(SafeDataReader dr)

        {

 

            //...


            _AllocationDetailList.ListChanged += new ListChangedEventHandler(AllocationDetailList_ListChanged);

 

            MarkOld();

 

        }

looks like the Businessbase object for the child. But there is no ListChanged event on BusinessBase.

Similarly, if it were in BusinessListBase, the event that the handler is pointing to;

        void AllocationDetailList_ListChanged(object sender, ListChangedEventArgs e)

        {

            PropertyHasChanged("--some parent property--");

        }

Has no PropertyHasChanged event in BusinessListBase????!

I am totally confused, having seen this solution cited in numerous posts, but have explicit instructions as to where the code should be placed.

I do consider myself a fairly experienced developer, and think this should be kids play. But the snippets all seem to be vague ...

Thanks,

gajit.

 

 

 

 

 

gajit replied on Thursday, March 26, 2009

Never mind.

Further experimentation gave me the answer.

I posted the resolution (in VB) here;

http://forums.lhotka.net/forums/32156/ShowThread.aspx#32156

 

Thanks.

 

ajj3085 replied on Friday, March 27, 2009

Well, if at all possible, upgrading to Csla 3.6.1 will be a huge help, if you convert all your child BO properties to the new Managed Properties supported by Csla.

The advantage is that most of the plumbing code (overriding IsDirty, hooking event handlers, etc) will be handled for you automatically.  The code reduction and elimination of the need for this code should be of great help to you.  I think Managed Properties are possibly the best new feature of Csla.net.

Copyright (c) Marimer LLC