Updating references OnDeserialized

Updating references OnDeserialized

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


tetranz posted on Thursday, February 28, 2008

I should be clear on this by now but unfortunately I'm not. It involves what I need to do with "contained" objects when the root is deserialized.

I have an object called Order. It has three addresses, Shipping, Billing and Contact. Each has the usual properties such as Name, Address1, Address2, City, State Zip etc. All these properties are directly bound to controls on the "Order" form of my WinForms app. The Order class has standard CSLA properties for ShippingName, ShippingAddress1, ShippingAddress2 ..., BillingName, BillingAddress1, BillingAddress2 ..., ContactName ... etc. i.e, the address properties are "flat" for anyone looking into Order from the outside.

Inside the Order class I have a private class called Address. It's a very simple class with public String fields. There are three instances of Address referenced with private variables within Order, _shipAddress, _billAddress and _contactAddress. Properties of these instances are used as the instance variables in the properties of Order described above. i.e, the instance "variable" for ShippingName is _shipAddress.Name rather than a direct string called_shipAddressName. The main reason for using the Address objects is so that I can put them in a Dictionary. That lets me easily grab an address by type i.e, _addresses[addressType]. Without that I'd need some ugly switch statements.

Most of those details might be irrelevant to my main question.

What do I need to do in OnDeserialized in my Order object? This will happen after I save because of the Clone. I'm confused about the references I have to the Address objects. Will _shipAddress still be a valid reference?  I think I need to connect _shipAddress, _billAddress and _contactAddress to what are now new instances of Address but I'm not sure how to do that.

Thanks for any help
Ross

tetranz replied on Thursday, February 28, 2008

I probably should have mentioned that I'm running CSLA 3.0.4

RockfordLhotka replied on Thursday, February 28, 2008

All references should remain intact unless you've marked the field with the NonSerialized attribute.

Events should be broken and must be re-hooked.

Typically that's all you need to do in OnDeserialized is rehook any events and re-establish any NonSerialized references.

Circular references should be broken by using NonSerialized. Technically they work, but people have found that they cause radical expansion of the serialized byte stream - this is why CSLA marks the parent references as NonSerialized.

Multiple references (non-circular) to a single object are not a problem - those references should automatically remain intact.

JohnB replied on Thursday, February 28, 2008

This may be a bit off topic but I was curious to get feedback regarding OnDeserialized and the number of times this method gets called during a Save. I've read in the forum where Rocky mentioned that it was three (3) times during normal operation but I was trying to learn more about this behavior.

In my case, I know that OnDeserialized gets called three times during every save operation. I am a little concerned that my process for reattaching the events is also happening every time. As far as I can tell there are no issues and I do know that I am removing handlers for every add handler. So the questions is, is there a better way?

My standard override as such:

Protected Overrides Sub OnDeserialized(ByVal context As System.Runtime.Serialization.StreamingContext)
    MyBase.OnDeserialized(context)
    
    '-- Reattach events etc.   
End Sub

Thanks,
John

RockfordLhotka replied on Thursday, February 28, 2008

Three?

 

Well let’s see.

 

If the data portal is running in local mode:

 

1.       The object is cloned (1)

2.       The clone is saved to the database

3.       The clone replaces the original object

 

If the data portal is running in remote mode:

 

1.       The object is effectively cloned to the app server (1)

2.       The clone is saved to the database

3.       The clone is effectively cloned back to the client (2)

 

The only case where, maybe, you wouldn’t need the events re-hooked is step 1 in the remote data portal scenario – because maybe you don’t use or care about the events on the app server. But you might care about them too, because more can happen on the app server than simply saving data to the database – especially these days when you might run a workflow there or something.

 

But step 1 in the local data portal and step 3 in the remote data portal scenario are what keeps the events working on the client for databinding, so they are unavoidable.

 

Rocky

JohnB replied on Thursday, February 28, 2008

I guess I should have included that I am working in remote mode and we subclassed BusinessBase.

So if I understand what you've said, I should see 2, not 3?

Here is a partial dump of my log file from the moment I hit save:
2008-02-28 23:47:04 INFO  [barMgr_SaveItemClick]
2008-02-28 23:47:04 INFO  BusinessBase [OnSerialized]
2008-02-28 23:47:04 INFO  BusinessBase [OnDeserialized]
2008-02-28 23:47:04 INFO  BusinessBase [OnSerialized]
2008-02-28 23:47:04 INFO  BusinessBase [OnDeserialized]
2008-02-28 23:47:04 INFO  BusinessBase [OnSerialized]
2008-02-28 23:47:18 INFO  BusinessBase [OnDeserialized]

Someting else, how can I verify for certain where the object is during this event? Client or Server?

Thanks,
John

ajj3085 replied on Friday, February 29, 2008

John,

You can determine the objects location by using the applicationContext.ExecutionLocation.  If you're running a local data portal though, this will always be client.

Also, you may want to write the stack trace so you can see what is causing the serialization.  system.diagnostics.stacktrace.

HTH
andy

rsbaker0 replied on Friday, February 29, 2008

ajj3085:
John,
You can determine the objects location by using the applicationContext.ExecutionLocation.  If you're running a local data portal though, this will always be client.
...

Since you mentioned this, what is the "correct" way to know if you are on the "server" side of the data portal, even when using the local data portal?

(I mainly want to know so I can put some debug asserts in that will flag things that shouldn't be called client side that would break with a remote data portal but work with the local data portal)

 

 

ajj3085 replied on Friday, February 29, 2008

I'm not sure there is a way short of creating your own flag.

RockfordLhotka replied on Friday, February 29, 2008

I was referring to 3.0 with autoclone on, or 3.5 by default – and assuming you aren’t manually calling Clone.

 

If you follow the pre-3.0 model (like shown in the book) and do a manual clone, then you’d get 3 – yours, and the two from the data portal.

 

This is why I added autoclone, to keep it to 1 for local and 2 for remote – and to eliminate the need for you to call Clone yourself.

 

Rocky

JohnB replied on Friday, February 29, 2008

RockfordLhotka:
I was referring to 3.0 with autoclone on, or 3.5 by default – and assuming you aren’t manually calling Clone.

If you follow the pre-3.0 model (like shown in the book) and do a manual clone, then you’d get 3 – yours, and the two from the data portal.

This is why I added autoclone, to keep it to 1 for local and 2 for remote – and to eliminate the need for you to call Clone yourself.

Ok, since I keep leaving out some details that may be important. I am using ver 3.0.3 and I am calling Clone in my override of SaveItem for ERLB.

This is what happens during the save operation:
(Using Csla.ApplicationContext.ExecutionLocation to determine location as suggested)
OnSerialized: Client
OnDeserialized: Client
OnSerialized: Client
OnDeserialized: Client
OnSerialized: Client
OnDeserialized: Server
OnSerialized: Server
OnDeserialized: Client


Here is what I am doing in my ERLB:

Public Sub SaveMyItem(ByVal objectToSave As MyBO)
    '-- Capture the objects index value within the collection which we will use to later save the object
    Dim pItemIndex As Int32 = Me.IndexOf(objectToSave)

    '-- We need to clone the object before applying any edits so that we can recover the original object should the save fail for
    '-- any reason. General exception, validation or data portal exception.
    Dim pMyBOClone As MyBO = objectToSave.Clone

    objectToSave.ApplyEdit()

    '-- Attempt to save the object
    Try
        MyBase.SaveItem(pItemIndex)

    Catch ex As DSCsla.Validation.ValidationException
        '-- Most likey a server side validation exception.
        '-- Set the object in the list back to the non-saved business object.
        '-- This object will contain the broken rules describing the reason for
        '-- the validation exception.
        Me.Item(pItemIndex) = DirectCast(ex.BusinessObject, MyBO)

        Throw ex

    Catch ex As Exception
        '-- Saved failed for some reason. Set the object back to the original close and bubble up the exception
        Me.Item(pItemIndex) = pMyBOClone

        Throw ex
    End Try

End Sub

Public Overrides Sub SaveItem(ByVal index As Integer)
    '-- Do nothing
End Sub

So, is it possible that Clone is happening twice on the client? Once by me and the other by the framework(autoclone)?

JohnB replied on Friday, February 29, 2008

Just wanted to add another note to this. It appears that the extra OnSerialized & OnDeserialized calls on the Client are do to the fact that I am calling Clone as well as the framework performing an auto clone.

So I can live with the extra call because I need to Clone so I can recover my object after ApplyEdit was called. This way I can allow the user to undo the changes they made. I don't believe that I can retrieve the auto cloned object given the code I have listed above.

Thanks,
John

jh72i replied on Friday, February 29, 2008

might be little off topic but i was just going to post to ask a related question.......say i have a collection of objects.....now each object be auto hooked to Child_PropertyChanged just but default.....now say i want to copy one of those objects to a new (to make the question easier) non-csla list......how can i get a copy of the object WITHOUT its _serializableChangedHandlers and _nonSerializableChangedHandlers being populated with the old collection event sink?

 

Copyright (c) Marimer LLC