Mar
26
2010

How to make XPO only update changed fields

User Rating: / 5
PoorBest 

A recent support center question regarding the fact that XPO sends and UPDATE SQL command that includes all fields regardless if they have changed or not, pointed to a rather unhelpful answer.

I too required this functionality and after a bit of trial and error got it all working, so thanks to Martin’s question I will post my answer here ;)

1. BaseObject

My first recommendation when using XPO is to make your own “Base” class (which inherits from one of XPO’s base classes such as XPBaseObject, XPLiteObject, XPCustomObject or XPObject ) which all “YOUR” objects will inherit from.

In our example we will declare a class called BaseObject which will inherit from XPObject.

XPO_BaseObject

This will allow us to put common functionality that will apply to all your business classes.

 

 

1.1 ChangedMembers

Now we will need a place to store the changed properties as they occur so when it comes to saving we will see what has changed.

    Private _ChangedMembers As DevExpress.Xpo.Metadata.Helpers.MemberInfoCollection
    Public ReadOnly Property ChangedMembers() As DevExpress.Xpo.Metadata.Helpers.MemberInfoCollection
        Get
            If _ChangedMembers Is Nothing Then
                _ChangedMembers = New DevExpress.Xpo.Metadata.Helpers.MemberInfoCollection(ClassInfo)
            End If
            Return _ChangedMembers
        End Get
    End Property

 

1.2 Overriding OnChanged

Now we have somewhere to store ChangedMembers we need to “monitor” our object and store the changes as they occur. The OnChanged method allows such as task so lets override it.

    Protected Overrides Sub OnChanged(ByVal propertyName As String, ByVal oldValue As Object, ByVal newValue As Object)
        MyBase.OnChanged(propertyName, oldValue, newValue)

        If Not Me.IsLoading Then
            Dim Member As DevExpress.Xpo.Metadata.XPMemberInfo = ClassInfo.GetPersistentMember(propertyName)
            If Member IsNot Nothing AndAlso Not ChangedMembers.Contains(Member) Then
                ChangedMembers.Add(ClassInfo.GetMember(propertyName))
            End If
        End If
    End Sub

 

1.3 Overriding OnSaved

So we have something to hold our changes, mark whats changed and now we need something to say that the changes aren’t changes anymore. We do this with the OnSaved method which is called after the object has been persisted.

   Protected Overrides Sub OnSaved()
        MyBase.OnSaved()

        ChangedMembers.Clear()
    End Sub

 

1.4 BaseObject Conclusion

So lets see the full BaseObject class now

Imports System
Imports DevExpress.Xpo

<NonPersistent()> _
Public Class BaseObject
    Inherits XPObject

    Public Sub New(ByVal session As Session)
        MyBase.New(session)
        ' This constructor is used when an object is loaded from a persistent storage.
        ' Do not place any code here.			
    End Sub

    Public Overrides Sub AfterConstruction()
        MyBase.AfterConstruction()
        ' Place here your initialization code.
    End Sub

    Private _ChangedMembers As DevExpress.Xpo.Metadata.Helpers.MemberInfoCollection
    Public ReadOnly Property ChangedMembers() As DevExpress.Xpo.Metadata.Helpers.MemberInfoCollection
        Get
            If _ChangedMembers Is Nothing Then
                _ChangedMembers = New DevExpress.Xpo.Metadata.Helpers.MemberInfoCollection(ClassInfo)
            End If
            Return _ChangedMembers
        End Get
    End Property

    Protected Overrides Sub OnChanged(ByVal propertyName As String, ByVal oldValue As Object, ByVal newValue As Object)
        MyBase.OnChanged(propertyName, oldValue, newValue)

        If Not Me.IsLoading Then
            Dim Member As DevExpress.Xpo.Metadata.XPMemberInfo = ClassInfo.GetPersistentMember(propertyName)
            If Member IsNot Nothing AndAlso Not ChangedMembers.Contains(Member) Then
                ChangedMembers.Add(ClassInfo.GetMember(propertyName))
            End If
        End If
    End Sub

    Protected Overrides Sub OnSaved()
        MyBase.OnSaved()

        ChangedMembers.Clear()
    End Sub
End Class

 

2. XtraUnitOfWork

Now we have a place to store our ChangedMembers and our object is “identifying” which members are changing. Now we need tell the “DataLayer” only about these changes. Our UnitOfWork acheives this by overriding the GetPropertiesListForUpdateInsert method (Technically you can Inherit from Session, but as per XPO Best Practices use of Session isn’t “recommended”).

Imports DevExpress.Xpo
Imports DevExpress.Data.Filtering

Public Class XtraUnitOfWork
    Inherits UnitOfWork

    Public Sub New(ByVal dictionary As Metadata.XPDictionary)
        MyBase.New(dictionary)
    End Sub

    Public Sub New(ByVal layer As IDataLayer, ByVal ParamArray DisposeOnDisconnect() As IDisposable)
        MyBase.New(layer, DisposeOnDisconnect)
    End Sub

    Public Sub New()
        MyBase.New()
    End Sub

    Protected Overrides Function GetPropertiesListForUpdateInsert(ByVal theObject As Object, ByVal isUpdate As Boolean) As Metadata.Helpers.MemberInfoCollection
        'Check if the Object is our "change tracking object"
        If TypeOf theObject Is BaseObject Then
            Dim ci As Metadata.XPClassInfo = GetClassInfo(theObject) 'obtain the class info so we can walk through the members
            Dim list As Metadata.Helpers.MemberInfoCollection = New Metadata.Helpers.MemberInfoCollection(ci) 'obtain a list of members
            Dim count As Integer = 0

            For Each m As Metadata.XPMemberInfo In MyBase.GetPropertiesListForUpdateInsert(theObject, isUpdate)
                'If it is a servicefield this is required (OID, GCRecord etc)
                If TypeOf m Is Metadata.Helpers.ServiceField Or CType(theObject, BaseObject).ChangedMembers.Contains(m) Then
                    list.Add(m)
                End If
            Next

            'return out list of "changed" members (plus the service fields)
            Return list
        Else
            Return MyBase.GetPropertiesListForUpdateInsert(theObject, isUpdate)
        End If
    End Function
End Class

 

3. Customer Class

Lets just create a quick test class, we’ll call it Customer

Imports System
Imports DevExpress.Xpo

Public Class Customer
    Inherits BaseObject

	Public Sub New(ByVal session As Session)
		MyBase.New(session)
		' This constructor is used when an object is loaded from a persistent storage.
		' Do not place any code here.			
	End Sub

	Public Overrides Sub AfterConstruction()
		MyBase.AfterConstruction()
		' Place here your initialization code.
    End Sub

    Private _company As String
    <Size(254)> _
    Public Property Company() As String
        Get
            Return _company
        End Get
        Set(ByVal Value As String)
            SetPropertyValue("Company", _company, Value)
        End Set
    End Property

    Private _webAddress As String
    <Size(254)> _
    Public Property WebAddress() As String
        Get
            Return _webAddress
        End Get
        Set(ByVal Value As String)
            SetPropertyValue("WebAddress", _webAddress, Value)
        End Set
    End Property

    Private _notes As String
    <Size(SizeAttribute.Unlimited)> _
    Public Property Notes() As String
        Get
            Return _notes
        End Get
        Set(ByVal Value As String)
            SetPropertyValue("Notes", _notes, Value)
        End Set
    End Property
End Class

Which is when we look at a the diagram we can see it derives from BaseObject which derivces from XPObject

XPO_CustomerObject

4. Test it out

Setup your XPO tracing (http://www.devexpress.com/Support/Center/kb/p/A2572.aspx) and open an object change one field and CommitChanges on your XtraUnitOfWork, you should find only the change is persisted.

Here is a complete demo of the above classes

DOWNLOAD HERE

5. NestedUnitOfWork Consideration

I did miss out some extra information above as I wanted you to understand how it all works before having to think about even more stuff ;)

When we override the OnSaved() method and clear the ChangedMembers collection, we have to take into account another awesome feature of XPO, NestedUnitOfWork.

Basically you can “CommitChanges” on a NestedUnitOfWork and at this stage it hasn’t actually “persisted’ the data to the database, but rather “persisted’ the changes to the “parent” UnitOfWork.

So to make sure that we “track” the changes we need to add this code before the ChangedMembers.Clear() line

So now the OnSaved() method should look like this

    Protected Overrides Sub OnSaved()
        MyBase.OnSaved()

        If TypeOf Session Is NestedUnitOfWork Then
            Dim parentitem As BaseObject = CType(Session, NestedUnitOfWork).GetParentObject(Me)
            For Each ChangedProperty As DevExpress.Xpo.Metadata.XPMemberInfo In ChangedMembers
                If Not parentitem.ChangedMembers.Contains(ChangedProperty) Then
                    parentitem.ChangedMembers.Add(ChangedProperty)
                End If
            Next
        End If
        ChangedMembers.Clear()
    End Sub

I have included this code already in the demo.

Comments  

 
0 # Dennis 2010-03-26 00:32
Totally awesome post. Totally.
Reply | Reply with quote | Quote
 
 
0 # Michael Proctor [Dx-Squad] 2010-03-26 00:34
Hi Dennis,
Thanks for stopping by, glad it was helpful.

Regards, Michael
Reply | Reply with quote | Quote
 
 
0 # Sean Kearon 2010-03-26 01:58
I agree with Dennis - this is awesome stuff! DevExpress should make it the default behaviour too.

Also, the C# versions of 1.2, 1.3 shows VB.
Reply | Reply with quote | Quote
 
 
0 # Michael Proctor [Dx-Squad] 2010-03-26 07:14
Thanks for the heads up on the C# conversions, as you can imagine it is a pain to keep both versions but the C# people seem to have issues reading English (VB.NET) so I have to help them out
Reply | Reply with quote | Quote
 
 
0 # Mohsen 2010-03-26 02:44
Very nice job Michael.
Thanks for sharing this.
Regards,
Mohsen
Reply | Reply with quote | Quote
 
 
0 # drew 2010-03-26 09:59
awesome.. i am seeing a large increase in bandwidth since flipping an app to xaf and this seems like a good place to start in looking to slim down the app.. many thanks!, drew..
Reply | Reply with quote | Quote
 
 
0 # Michael Proctor [Dx-Squad] 2010-03-26 10:08
Hi drew,

I understand what you mean about bandwidth usage, my app is used over .NET remoting over ADSL connections so every KB counts.

In my application (not XAF) I use XPView instead of XPCollections and display readonly lists to users to which a detail form is required to edit, the advantage here is you only get the columns you require, the other advantage here is with "Related" fields such as Status and Type etc, with the XPView I return what ever relation field I want like Status.Title, whereas with XPCollection it returns you main object then had to grab the Status object as well to give you Status.Title which I find massively excessive.

In my app, I have it so when I go to load the XPView I obtain all the fieldsnames from my XtraGrid Columns that are visible and only add those columns to my XPView, this means if the user only has 5 columns on the grid I receive 5 fields, if they had all 50 of em they download all 50.

Unfortunately XAF doesn't support XPViews, it relies on the use of ServerMode, which in my case wasn't possible as I am connected to a legacy Access database.

Hopefully this helps out a little for you.
Reply | Reply with quote | Quote
 
 
0 # Tiendq 2010-04-09 13:20
Surely this is a very nice post, but I don't know why this feature has been not built in XPO itself. I think persisting only changed fields must be an intrinsic requirement for every ORM product.

I just looked at BaseObject class of XAF and quite surprisingly, it didn't implement this feature too.
Reply | Reply with quote | Quote
 
 
0 # Michael Proctor [Dx-Squad] 2010-04-09 13:26
Hi,

I agree it seems like a "normal" thing to do, why persist values that didn't change, however please following this forum thread http://community.devexpress.com/forums/t/87479.aspx

In there Gary does point out that your would have to watch out with your Concurrency, as you could end up with 2 people with 2 incorrect views of the data (but this is only if you use XPLiteObject without OptimisticLocki ng)

I suppose it comes down to "lowest" common denominator, the big plus is that XPO gives us the functionality to extend it and make it do what we want ;)

Please also look at the forum posts regarding how to ensure this works with Persisted ReadOnly fields (PersistentAlia s) there is an update, unfortunately I haven't updated this post yet.

Also near the end of the post is an IMPORTANT NOTE regarding New Objects and default values, I would suggest performing a check and if the object is new to persist the entire object and not just changed (again code is on that forum thread and I will update this post at some stage)

Thanks for popping by
Reply | Reply with quote | Quote
 
 
0 # Sean Kearon 2010-04-09 17:19
Quoting Michael Proctor [Dx-Squad]:
the big plus is that XPO gives us the functionality to extend it and make it do what we want


True, but it's worth noting that DX do reserve the right to change their API from time to time (for example, http://www.devexpress.com/Support/Center/p/B142652.aspx), so it's worth considering whether you can do what you need with XPO "out of the box" before extending.
Reply | Reply with quote | Quote
 
 
0 # Michael Proctor [Dx-Squad] 2010-04-09 20:00
Hi Sean,

Very true ;) however keep in mind that this isn't hacking as such :)

GetPropertyList ForUpdateInsert was made virtual for this purpose and, there is nothing stopping DX adding this functionality into the PersistentBase and Session classes, and our "implementations " wouldn't be effected, however could finally be removed and just use the base implementation.

I welcome DX to include this functionality within the suite, and infact just had a look through and the only mention is the original support ticket which made this function available.
http://www.devexpress.com/Support/Center/p/AS5031.aspx

So I have just made the suggestion again
http://www.devexpress.com/issue=S134356

Regards,

Michael Proctor
Reply | Reply with quote | Quote
 
 
0 # Tiendq 2010-04-09 13:25
Another question, how does it work with upcoming XAF 10.1, when all business objects are based on interface?
Reply | Reply with quote | Quote
 
 
0 # Michael Proctor [Dx-Squad] 2010-04-09 13:37
I would have to check with Gary, as although I have access to an early private beta, I haven't had a play with DC (Domain Components) yet.

Keep in mind that DC is "another" way you can have persistant objects, your current PersistantClass es will still work with XAF, just now XAF also supports Persistant Interfaces as well.

So ultimately you won't have a problem immediately, only if you move across to DC technology.

Will ask Gary the question ;)
Reply | Reply with quote | Quote
 

Add comment

Although I believe your free to say what you want, please don't abuse either myself or other peoples, be constructive.


Security code
Refresh

Should AussieALF stay bald?




Results

Latest Comments

My Twitter

Follow me on twitter