Hi,
working with CLSA.NET the way before v3.5 (without using the managed fields) OnPropertyChanged should be called every time a property setter changes a member field instance value. This marks the object instance as dirty and leads to the expected behavior. But when the UI (or user) sets the value back to its "old value" (without using CSLA.NET's undo) it causes unnecessary server updates by calling DataPortal_Update when the UI calls BusinessBase.Save(). This is because the object instance is still marked as "dirty" although the values are again equal or unchanged compared to the database. This is critical especially if the business object's properties binds to complex UI controls. Unfortunately "managed fields" don't seem to address this problem (but it would be easy to extend them in that way). To reduce unnecessary sever updates I implemented my own "managed fields" approach:
1. I defined a business data transfer object (BDTO) which maintains two hash tables. One for "old values" and one for the "new values" (Hashtable _oldValues, _newValues;).
2. _oldValues where set in any DataPortal_Update/_Insert/_Fetch/_Create members and stores the current database values (CSLA.NET definition used for calling BusinessBase.MarkOld()).
3. Every time when a property setter is called, my BusinessBase derived class checks again the old values. If the "new value" differs compared to the "old value" I inserted the "new value" into _newHashTable and raise the OnPropertyChanged event. If the "new value" is equal to the "old value" I removed the value from the _newValues and call OnPropertyChanged as well.
3. Finally I also check if _newValuesHashtable.Count==0. If so, I call BusinessBase.MarkClean() and BuseinessBase.MarkOld(). This prevents unnecessary server update requests when the UI calls BusinessBase.Save().
I was hoping that FieldManager does implement a similar approach. Unfortunately all protected Load/Set/GetProperty members are not declared virtual, so no elegant way seems to exist to extend FieldManagers behavior in that way. I really would like to use CSLA.NET managed fields for that, which makes the code look less complex and automates a lot. Do you have any idea how?
You should be able to create your own IFieldData<T> implementation that stores both the original and current values, and implements IsDirty to take those values into account.
You'll also need to create a subclass of PropertyInfo<T>, because that is where the FieldData instances are created. In other words, subclass PropertyInfo<T> and override NewFieldData() to return instances of your new IFieldData<T> implementation.
Finally, you'll need to create instances of your PropertyInfo<T> subclass when calling RegisterProperty() in all your business objects. That way the property manager will contain your custom objects, which will return your custom field data objects.
That was helpfull, Thanks Rocky!
RockfordLhotka:You should be able to create your own IFieldData<T> implementation that stores both the original and current values, and implements IsDirty to take those values into account.
You'll also need to create a subclass of PropertyInfo<T>, because that is where the FieldData instances are created. In other words, subclass PropertyInfo<T> and override NewFieldData() to return instances of your new IFieldData<T> implementation.
Finally, you'll need to create instances of your PropertyInfo<T> subclass when calling RegisterProperty() in all your business objects. That way the property manager will contain your custom objects, which will return your custom field data objects.
Rocky,
Is this something you might think about including in a future version in the base classes? Perhaps with a config option defaulted to disabled?
It should be as straightforward as adding a pre-dirty value placeholder and a boolean check when the value is set that if newValue = preDirtyValue at anytime then IsDirty is false again.
The way I see it is the only real overhead is the extra preDirty value.
This is something I could definately see myself using.
Thanks
Jack
Maybe, but not in the real near future.
The trick is that this doubles the size of the object on the
network in n-tier scenarios, so it isn’t something to be done lightly…
If I do it, it wouldn’t be a config option, as much as
perhaps alternate PropertyInfo<T> and FieldInfo classes that you could
use in your object code. In other words, you’d “opt in” by
using a different PI/FI when registering your properties – just like I’m
describing in this thread.
Rocky
Just another thought - if CSLA is tracking n-level undo changes then isn't the data already there if you call a beginEdit( )?
I had a quick look at the n-level undo section in the original book and since I haven't had enough caffeine this am I didn't try to hard but basically if the final value at ApplyEdit is the same as the value saved in the snapshot at BeginEdit then it hasn't truely changed.
If that is true, then is enough of the interface exposed that you can get at those original states to do a pre-IsDirty check to implement a IsReallyDirtyCheck?
Just throwing out an idea... sorry if I've brutally mispoken by not doing enough research - I should really be working :-)
thanks
jack
Opting in would be ideal. Because it would only double the size of the object if you registered *all* of your properties that way. I have specific fields where I need to know the original value. So I have a separate field for them in my BO anyway. In the future I could opt in and let CSLA manage them for me if I wanted to. And I could leave the other fields alone - opt out by registering them the original way.
Joe
Hi Rocky,
deriving from FieldData<T> is not sufficient, unfortunately. At least the Silverlight implementation of CSLA has to make sure that the additional "OriginalValue" member of the custom FieldData<T> class gets be serialized by the MobileFormatter (it usually gets initialized on the server side). This again can be reached by deriving from FieldDataManager and overriding OnGetState() and OnSetState(). But then I run in to the problem that the FieldDataManager property of BusinessBase is not declared virtual. So there is no way to set it to a custom FieldDataManager instance in a derived BB class. Can you make it virtual? It would be great if you could add this to the feature wish list.
Regards,
Andreas
Really?
I’d expect each FieldData<T> to be serialized
individually. Otherwise how can arbitrary numbers of new values be added to the
FIeldData object? It can’t be the collection’s responsibility to
understand all the goo that might be in each child object.
I didn’t write that code, so I’m speaking only from “what
should be”, not “what is” – but that’s what I’d
expect.
I just went and looked at the code, and unfortunately it doesn’t
do “what should be”. I think the current implementation was chosen
because of the performance implications of serializing more complex FieldData
objects.
Following this train of thought though – it is STILL not
the collection’s responsibility to serialize complex child objects. So I
think the answer is for FIeldDataManager’s serialization methods to
detect if the IFieldData object implements IMobileObject. If not (like now), it
will serialize Value. If the IFieldData object does implement IMobileObject,
then it will use normal child object serialization to serialize the IFieldData
object, allowing that object to be arbitrarily complex.
I’ll add this to bugtracker.
Rocky
From: Andreas
[mailto:cslanet@lhotka.net]
Sent: Sunday, January 18, 2009 2:25 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: Extending FieldManager to prevent
unnecessary server updates?
Hi Rocky,
deriving from FieldData<T> is not sufficient, unfortunately. At least
the Silverlight implementation of CSLA has to make sure that
the additional "OriginalValue" member of the custom
FieldData<T> class gets be serialized by the MobileFormatter (it usually
gets initialized on the server side). This again can be reached
by deriving from FieldDataManager and overriding OnGetState() and OnSetState().
But then I run in to the problem that the FieldDataManager property
of BusinessBase is not declared virtual. So there is no
way to set it to a custom FieldDataManager instance in a derived BB class.
Can you make it virtual? It would be great if you could add this to the
feature wish list.
Regards,
Andreas
Yes, I completely agree with you. The solution you described is much more elegant an extensible than the one I suggested. But FieldData<T> should implement IMobileObject by default. Otherwise FieldDataManager has to do an additional type check on each individual data field, which could probably result in significant performance penalties when transferring bigger chunks of data.
Andreas
It is important to remember that the cost of transferring data
over the wire is nearly always substantially higher than the cost of CPU
processing. Doing one quick type check to see if an object implements an
interface is not expensive, and avoiding the overhead of full serialization of
child objects in the simple case (where each child object is a simple wrapper
over a single value) is a powerful benefit.
Rocky
From: Andreas
[mailto:cslanet@lhotka.net]
Sent: Sunday, January 18, 2009 4:27 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: RE: Extending FieldManager to prevent
unnecessary server updates?
Yes, I completely agree with you. The solution you described is much more
elegant an extensible than the one I suggested. But FieldData<T> should
implement IMobileObject by default. Otherwise FieldDataManager has to do an
additional type check on each individual data field, which could probably
result in significant performance penalties when transferring bigger chunks of
data.
Andreas
I have not implemented this code - but I was playing around with it for a short time over the summer to see how you could track original values and modify IsDirty so that if you restore the original value the object is no longer dirty.
Feel free to use it if it helps.
=================================================================
Imports
Csla.CoreNamespace
BO<Serializable()> _
result =
DirectCast(DirectCast(0, Object), T)#End
Region Public Property OrigValue() As TPublic Overrides ReadOnly Property DefaultValue() As T
Get
Return _defaultValue
End Get
End Property
'My only real worry here, is that a subclass will need to detect that it contains a child object and handle that correctly - 'so it still isn't necessarily trivial to write a subclass.'But then again, your PropertyInfo subclass could make that determination and return a standard FieldData rather than a custom FieldData for child objects.
Protected Overrides Function NewFieldData(ByVal name As String) As Core.FieldManager.IFieldData
If GetType(IEditableBusinessObject).IsAssignableFrom(Me.Type) OrElse _
GetType(IEditableCollection).IsAssignableFrom(Me.Type) Then
Return New FieldData(Of T)(name)
Else
Return New MyFieldData(Of T)(name, _origValue)
End If
End Function
End Class
End Namespace
=================================================================
Imports
Csla.CoreNamespace
BO<Serializable()> _
End
Namespace===================================================================
Then in BO you could use code like this:
Private Shared AcctdescProperty As MyPropertyInfo(Of String) = _And in DataPortal_Fetch:
...
With dr...
Joe
You should not have to store these objects at all. CSLA should automatically
store your custom object types just fine, as long as they implement the right
interfaces or inherit from the existing classes.
In other words, your custom PropertyInfo<T> subclass will
be stored once per appdomain, just like the PropertyInfo<T> objects are
now.
And your custom PropertyInfo<T> will return an instance of
your custom FieldInfo<T>. The FieldInfo<T> objects will be stored
automatically on a per-business object basis just like the FieldInfo<T>
objects are now.
There is no way for you to change the way CSLA stores the
PropertyInfo<T> and FieldInfo<T> objects – that is not something
you should have to worry about.
Rocky
From: Andreas
[mailto:cslanet@lhotka.net]
Sent: Monday, November 10, 2008 10:50 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Extending FieldManager to prevent unnecessary
server updates?
Sorry
Rocky, but I still have one problem with your suggestion: FieldData and
PropertyInfo don’t have access to their owning BusinessBase instance.
PropertyInfo instance are static class properties and exist only once per
AppDomain. FieldData instances are being created by a call to
PropertyInfo.NewFieldData() inside of FieldDataManager.GetOrCreateFieldDat().
To implement the previous described “old/new value management†I have to store
these values in a container on per BusinessBase instance basis.
Copyright (c) Marimer LLC