Object Instance Changing Challenge (OICC) of CSLA

Object Instance Changing Challenge (OICC) of CSLA

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


Andreas posted on Friday, September 24, 2010

Hi,

one of the basic design principles of CSLA is that a new object instances is returned to the caller if a request is made to the server. There are a lot of good reasons for this. But there are also a lot reasons not to change the instances. I don’t want to start a pro and con discussion about this again. There are already a lot of forum entries which discuss this principal in detail.

The other side of the coin is the fact that a lot of errors can be caused with different instances of the “same” business objects before and after saving it. These errors caused us a lot of headache and project time. In every case these errors showed up on runtime only (if ever) and in almost every case we end up comparing the hash codes of the object instances to find out that somewhere in the application a control, ViewModel or object property still was referencing the old instance.
My intention of this post is to discuss alternatives to that principal and how to implement or extend CSLA to switch this behavior totally or if possible by configuration. I didn’t either start to analyze CSLA source for this nor did I start to implement anything. For now I can think of at least two ways:

1.     Using the Proxy Pattern: Every business object has a corresponding protected or internal “proxy class” BusinessRoot has one and only one instance to BusinessRootProxy
BusinessRoot delegates each and every call to BusinessRootProxy
If you call Save on BusinessRoot it delegates its calls to its internal BusinessRootProxy instances (_proxy). The implementation of BusinessRoot makes sure that he _proxy-Property gets updated with the new instance.

Pro: This solution can be implemented without changing CSLA’s behavior.
Cons: You have to maintain a class that is managing (and probably duplicating) all your properties and probably some behavior. But this can be widely automated or supported with code generation and code snippets or with tools like PostSharper.

2.     Serializing only CSLA’s FieldDataManager and updating BusinessRoot’s internal FieldDataManager instance after returning from the server. This of causes works only, if every relevant state of the BusinessRoot is maintained in managed fields. It is already necessary for Silverlight, so I think this could be extend for the internal CSLA Business classes as well. As far as I know the serialization already treats FieldDataManager as one of many properties of the business object. There are also other internal properties that need to be serialized like Context etc.

3.    

I know, I know, this only one little part of the truth and it is probably much more complex but it’s a starting point…

Regards,
Andreas

 

Andreas replied on Sunday, October 10, 2010

Hi,

I implemented the required behavior, which is now working as expected. Now we can share instances of business objects between modules without introducing memory leaks or update problems. The next step is to introduce it in some complex projects to see if it's stable.
For the implementation we needed the following
requirements

1.       All business objects need to use managed properties only.

2.       All business object instances need to have a unique identifier (we are using GUIDs as a default for this anyway).

        We implemented an Interface IEntity and IEntitySet in our base class (derived from BusinessBase<T>,  BusinessListBase<T>). We also needed to change some access modifiers in csla to make it work.

1.       I need access to some internal members of the FieldManager. I changed some of their access modifiers from internal to protected or public and added them as an explicit implementation to the interface IEntity. This makes sure that these properties are not visible as regular members in derived classes.

2.       I implemented a method called SaveChanges() in my base class. It calls Save() first. Then it creates two dictionaries collecting all child objects indexed by its unique id. One dictionary for the result of the Save operation and the for the original unsaved business object (this). I used the FieldDataManager.GetChildObjects() to find all objects in the object graph. Next I walk through all the child objects of the original objects graph and set the IFieldData.Value of all properties which Values are not IEntity or IEntitySet to the corresponding Value of the result object graph.

3.       At the end I have to loop through all child objects of the original graph again and call MarkOld() on each of them. It makes sure that OnPropertyChanged() is called on each property and the UI gets updated.

 

That’s it. Here's is my implementation:

 

         public void SaveChanges()
        {
            object result = this.Save();
            if(ReferenceEquals(this, result))
                return;

            Dictionary<int, IEntity> source = this.CollectChildEntities(result);
            Dictionary<int, IEntity> target = this.CollectChildEntities(this);

            foreach (KeyValuePair<int, IEntity> sourceEntity in source)
            {
                if (target.ContainsKey(sourceEntity.Key) == false)
                    continue;

                IList<IPropertyInfo> infos = sourceEntity.Value.FieldManager.GetRegisteredProperties();
                foreach (IPropertyInfo propertyInfo in infos)
                {
                    if (sourceEntity.Value.FieldManager.FieldExists(propertyInfo))
                    {
                        IFieldData sourceFieldData = sourceEntity.Value.FieldManager.GetFieldData(propertyInfo);
                        if(sourceFieldData.Value is IEntity || sourceFieldData.Value is IEntitySet)
                            continue;

                        IFieldData targetFieldData = target[sourceEntity.Key].FieldManager.GetOrCreateFieldData(propertyInfo);
                        targetFieldData.Value = sourceFieldData.Value;
                    }
                }
            }

            foreach (KeyValuePair<int, IEntity> targetEntity in target)
            {
                targetEntity.Value.MarkOld();
            }

            this.MarkOld();
        }
 

RockfordLhotka replied on Sunday, October 10, 2010

This is interesting, thank you for sharing.

Have you looked at the DiffGram sample? It does something not entirely different (though not as ambitious), though it doesn't actually clone the objects across the wire - just a subset of the object graph.

 

Andreas replied on Saturday, October 16, 2010

I looked at DiffGram sample and I think it's a much more general approach. But I am wondering if it will work with Silverlight because the DiffGram classes are not using managed properties?
There is one more problem that I might have with this approach. It’s the implementation of  ImportFrom. It calls MarkOld() at the end of each import. This is (at least in our scenarios) problematic because MarkOld() fires OnPropertyChanged on each property and this again fires the associated business rules before the full object graph is build up. This causes trouble because some complex business rules look up for values in other objects in the object graph (the parent or child) and these objects might not have been imported at that time. That’s why I am calling MarkOld() in my SaveChanges() implementation after the full object graph has been imported. What do you think?

 

RockfordLhotka replied on Saturday, October 16, 2010

CSLA doesn't run business rules based on the PropertyChanged event. The PropertyChanged event is for data binding, and really has nothing to do with the way CSLA invokes rules.

It is true that the diffgram objects would need to use managed backing fields to work on SL, and that could require a little effort.

Copyright (c) Marimer LLC