Extending FieldManager to prevent unnecessary server updates?

Extending FieldManager to prevent unnecessary server updates?

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


Andreas posted on Saturday, November 08, 2008

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?

 Andreas

RockfordLhotka replied on Saturday, November 08, 2008

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.

Andreas replied on Sunday, November 09, 2008

That was helpfull, Thanks Rocky!

 

Jack replied on Monday, November 10, 2008

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

RockfordLhotka replied on Tuesday, November 11, 2008

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

 

Jack replied on Tuesday, November 11, 2008

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

 

JoeFallon1 replied on Tuesday, November 11, 2008

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

 

Jack replied on Tuesday, November 11, 2008

By config option I mean't opt-in by Property, not a global switch.  Whether another subclass or an extra parameter in the Register process.

Andreas replied on Sunday, January 18, 2009

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 

 

RockfordLhotka replied on Sunday, January 18, 2009

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 

 



Andreas replied on Sunday, January 18, 2009

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

RockfordLhotka replied on Sunday, January 18, 2009

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



Andreas replied on Monday, November 10, 2008

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.

JoeFallon1 replied on Monday, November 10, 2008

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.Core
Imports Csla.Core.FieldManager

Namespace BO

<Serializable()> _
Public Class MyPropertyInfo(Of T)
 
Inherits PropertyInfo(Of T)

 
Private _origValue As T
 
Private _defaultValue As T

#Region " Constructors "

Public Sub New(ByVal name As String)
 
MyBase.New(name)
End Sub

Public Sub New(ByVal name As String, ByVal friendlyName As String)
 
MyBase.New(name, friendlyName)
  _defaultValue = GetDefaultValue()
End Sub

Public Sub New(ByVal name As String, ByVal friendlyName As String, ByVal defaultValue As T)
 
MyBase.New(name, friendlyName, defaultValue)
  _defaultValue = defaultValue
End Sub

Private Function GetDefaultValue() As T
 
Dim result As T
 
If GetType(T).Equals(GetType(String)) Then
   
result = DirectCast(DirectCast(String.Empty, Object), T)
  
ElseIf GetType(T).Equals(GetType(Integer)) OrElse _ 
   
GetType(T).Equals(GetType(Long)) OrElse _ 
   
GetType(T).Equals(GetType(Decimal)) OrElse _ 
   
GetType(T).Equals(GetType(Short)) OrElse _ 
   
GetType(T).Equals(GetType(Byte)) Then

    result = DirectCast(DirectCast(0, Object), T)
 
Else
   
result = Nothing
 
End If
 
Return result
End Function

#End Region

Public Property OrigValue() As T
  Get
   
Return _origValue
  End Get
 
Set(ByVal value As T)
   
_origValue = value
 
End Set
End Property

Public 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.Core
Imports Csla.Core.FieldManager

Namespace BO

<Serializable()> _
Public Class MyFieldData(Of T)
 
Inherits FieldData(Of T)
 
Implements IFieldData(Of T)

  Private _origValue As T

Public Sub New(ByVal name As String, ByVal origValue As T)
 
MyBase.New(name)
  _origValue = origValue
End Sub

Public ReadOnly Property OrigValue() As T
 
Get
   
Return _origValue
 
End Get
End Property

Public Overrides Property Value() As T
 
Get
   
Return MyBase.Value
 
End Get
 
Set(ByVal value As T)
   
MyBase.Value = value 'sets IsDirty = True
   
If _origValue IsNot Nothing AndAlso MyBase.Value.Equals(_origValue) Then
     
MarkClean() 'sets IsDirty to False
   
End If
 
End Set
End Property

Public Overrides ReadOnly Property IsDirty() As Boolean
 
Get
   
If _origValue IsNot Nothing AndAlso MyBase.Value.Equals(_origValue) Then
     
Return False
   
Else
     
Return MyBase.IsDirty
   
End If
 
End Get
End Property

End Class

End Namespace

===================================================================

Then in BO you could use code like this:

Private Shared AcctdescProperty As MyPropertyInfo(Of String) = _ 
 
CType(RegisterProperty(Of String)( _
 
New MyPropertyInfo(Of String)("Acctdesc", "Account description")), MyPropertyInfo(Of String))

Public Property Acctdesc() As String
 
Get
   
Return GetProperty(Of String)(AcctdescProperty)
 
End Get
 
Set(ByVal value As String)
    SetProperty(
Of String)(AcctdescProperty, value)
 
End Set
End Property

And in DataPortal_Fetch:

...

With dr
 
If .Read() Then
   
AcctdescProperty.OrigValue = Trim(.GetString("acctdesc"))
   
Me.LoadProperty(Of String)(AcctdescProperty, Trim(.GetString("acctdesc")))

...

Joe

RockfordLhotka replied on Tuesday, November 11, 2008

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