How to Make CopyState Work With a Tree?

How to Make CopyState Work With a Tree?

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


josh.kodroff posted on Monday, June 25, 2007

I have a CSLA class representing an equation, and the terms are stored in a tree.  The tree node includes references to both parent and children  (necessarily, I think).  I believe this is the source of an error (StackOverflowException) I'm experiencing when databinding.  I figure that the CopyState call is not real understanding of the circular references.

How do I get around this?  Note that n-level undo is not especially important to the app I'm working on, so I can sacifice it for this particular class if necessary.

Thanks!
JK

RockfordLhotka replied on Monday, June 25, 2007

You must mark any parent field with the NotUndoable attribute so n-level undo ignores that field.

You may need to (and probably should) mark parent fields as NonSerialized as well. Though the serializer honors circular references, they do tend to expand the size of the serialized byte stream. If you mark the field as NonSerialized, then make sure to override the OnDeserialized method and re-hook the reference, otherwise your references will get lost during the serialization/deserialization process.

josh.kodroff replied on Monday, June 25, 2007

Not that it sounds all that hard, but I haven't written any handlers for serialization events before.

Can I ignore (at least for the time being) if I don't plan to use n-level undo?

Hearing that probably breaks your heart, knowing what a PITA it was to implement.  Smile [:)]

Oh, and that's now at least a six-pack I owe you.

RockfordLhotka replied on Monday, June 25, 2007

If you use Windows Forms data binding you must make n-level undo work. No option.

 

(well, in CSLA 3.0 there’s an option because you can set DisableIEditableObject to True to block automatic use of undo by data binding, but in that case you’ll find that the UI doesn’t quite work like you expect unless YOU call BeginEdit/CancelEdit/ApplyEdit appropriately…)

 

But to make n-level undo work all you need to do is put the NotUndoable attribute on your parent fields.

 

The serialization issue is only important if you use a remote data portal server or call Clone (which is recommended when saving your objects when using a local data portal configuration).

 

In other words, the n-level undo fix is trivial. The serialization fix is slightly harder, but is necessary for a couple common/important persistence scenarios.

 

Rocky

josh.kodroff replied on Tuesday, July 10, 2007

Rocky,

Can you point me to an example of how a tree (or any data structure with circular references) would be deserialized?  I searched the PTracker solution, but found nothing.

JK

RockfordLhotka replied on Tuesday, July 10, 2007

You'll notice that I never actually do serialization or deserialization. Instead I use the BinaryFormatter or NetDataContractSerializer to do any real (de)serialization.

n-level undo serializes individual objects only - never an actual graph. That avoids the complexity.

I did once try to write a complete serializer. Serialization is relatively easy. Deserialization (if you ignore ISerializable) is tricky, but possible - even with circular references. But as soon as ISerializable enters the picture you are in serious difficulty, because you need to be able to create objects without running their constructor or any other initialization code. Without that ability, you can't avoid an ugly circular reference issue.

Long after I abandoned that project (because I wanted an XML serializer, and got one with WCF), I did find the answer. Unfortunately I don't remember the type/method name now, but buried deep in .NET there's a type that has a method that can create an object without initializing that object. That's the method used by BF and NDCS to create an object, and then they manually invoke the "constructor" after they've done all the fix-ups needed to resolve circular references.

It is also the case that .NET includes some serialization helper objects. If you use Reflector against the BF you can find them. I didn't use them, because I wanted to see how it worked from the bottom up, but using them can help a lot, because they do the fix-up work for you, etc.

Of course my answer could be totally going in a different direction from the intent of your question...

josh.kodroff replied on Tuesday, July 10, 2007

Earlier in this thread, you wrote that I'd need to write a Deserialization handler so that I can make the child pointer to parent work.  I have no idea how to do this, so I was wondering if you had a simple example.

I only need to deserialize this one class (an n-ary tree).




RockfordLhotka replied on Tuesday, July 10, 2007

Yeah, I was afraid I was going off into left field with my last answer J

 

If you look at CSLA itself (specifically BusinessListBase) you’ll see what I mean. It has a deserialization handler where it loops through its children to call SetParent(). This re-links the child’s parent reference back to the collection.

 

Rocky

josh.kodroff replied on Tuesday, July 10, 2007

So, here's the relevant object structure:

Public Class Study
    mEquations as StudyEquationList

Public Class Equation
    mRoot as Equation Node

Public Class EquationNode
    <NonSerialized()> <NotUndoable()> Private mParent As EquationNode
    Private mChildren As EquationNodeList = EquationNodeList.NewEquationNodeList

And I put this in the EquationNode class:

    Protected Overrides Sub OnDeserialized(ByVal context As System.Runtime.Serialization.StreamingContext)
        For Each tempItem As EquationNode In mChildren
            tempItem.ParentNode = Me
        Next
        MyBase.OnDeserialized(context)
    End Sub

But it's not being called when anywhere.  I expect it to be called in the following (from my form's save button code):

            _study.ApplyEdit()
            _study = _study.Save
            _study.BeginEdit()

RockfordLhotka replied on Tuesday, July 10, 2007

I think you are missing some relevant bits.

 

Either you are

 

<Serializable()> _

Public Class Study

  Inherits BusinessBase

 

Or you are

 

<Serializable()> _

Public Class Study

 

If you inherit from BusinessBase, then you are part of the n-level undo cycle itself, and your object won’t be serialized by n-level undo. Instead, UndoableBase uses reflection to trap your fields.

 

If you do NOT inherit from BB, then you will be serialized using the BinaryFormatter, but you won’t be able to override OnDeserialized() because that comes from BB. Instead, you’ll need to declare a method with an attribute (look at BB for an example) so the BinaryFormatter calls you directly.

 

In the former case, where you do inherit from BB, OnDeserialized() will only be called due to a Clone() or Save() call (pretty much). You don’t typically need to reset the parent on a CancelEdit() or ApplyEdit() because the NonUndoable attribute tells n-level undo to entirely ignore the specified field. This means it is never touched – not cleared or anything.

 

So when inheriting from BB you do need to reset the parent on deserialization. You do not need to reset the parent due to n-level undo.

 

Rocky

josh.kodroff replied on Tuesday, July 10, 2007

All classes are <Serializable()> and inherit from BusinessBase.

The problem is, when I call save, my mParent for the EquationNode turns to nothing.  Something is happening in those 3 lines (apply edit, save) that turns my mParents  to nothing.

JK

RockfordLhotka replied on Tuesday, July 10, 2007

Right, Save() will cause serialization and deserialization. That should cause your method to run.

 

If you are using a local data portal, you are probably saving a clone – so it is the Clone() call that actually triggers the process. If you are using a remote data portal it is the data portal that triggers the process.

 

If you are using a local data portal and not calling Clone(), then no serialization would occur.

 

Rocky

RockfordLhotka replied on Tuesday, July 10, 2007

Looking at your code, you are not doing a Clone(). So if you are using a local data portal then no serialization occurs. That probably explains why the method is never invoked Smile [:)]

But if that is the case, then neither serialization nor undo are clearing the field, so you must have code (perhaps in your DP_XYZ methods) that is somehow clearing the field.

Copyright (c) Marimer LLC