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.
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