May
10
2011

XPO Lightweight Associations

User Rating: / 0
PoorBest 

EDIT: Added a little extra to the Workaround scenario by saying we want to open a "detail" object and have a grid with the associated records in it, displaying a FK from another association within that object. such as PurchaseOrder > Agreement > Sale, whereby you want to open the PurchaseOrder and list all associated Agreements displaying the Sale.Id in the grid, but without downloading the Sale Object.

One issue I still have with XPO is when using Associations you end up with alot of extra data retrieved from your database that you may not even need to access.

In some scenarios all I need access to is the Foreign Key and not all the other data of that object.

Scenario

Say you have an application that lists and edits Blog posts, when the application starts you obtain a User object containing the Id, Fullname and all the other data and within your Blog object you have a CreatedBy property which is associated with the User object. This will store a Integer (or GUID) in the Blog table as the FK.

By default when you get a XPCollection of Blogs you would also have joins for the CreatedBy to grab all the User objects as well. We can stop this from happening by using the DelayedProperty pattern.

Problem

When users try to edit the blog, we want to check if the current user logged in created this blog record. If you do MyBlogObject.CreatedBy.Id = LoggedInUserId you will have to download the entire User Object to do this. Which is crazy as all we needed was the FK which is stored in the Blog record already.

Workaround?

Now I did some more research into DelayedProperties, we can do some funky Reflection calls and obtain the “Key” from the XPDelayedProperty class. Unfortunately as most of this members are sealed/internal you have to use Reflection to access them.

This is what I ended up with

Public Function GetRawId(Of T)(ByVal Field As OperandProperty, Optional ByVal DefaultValue As T = Nothing) As T
	Try
		If Not Me.IsLoading And Not Me.IsSaving Then
			Dim mi As Metadata.XPMemberInfo = ClassInfo.GetMember(Field.PropertyName)
			Dim GetCustomPropertyValue As Reflection.MethodInfo = Me.GetType.GetMethod("GetCustomPropertyValue", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
			If GetCustomPropertyValue IsNot Nothing Then
				Dim result As Object = GetCustomPropertyValue.Invoke(Me, New Object() {mi})
				If result IsNot Nothing Then
					Dim xpdp As XPDelayedProperty = result
					Dim value As Object = Nothing
					If xpdp.IsLoaded AndAlso xpdp.Value IsNot Nothing Then
						value = CType(xpdp.Value, BaseObject).ClassInfo.GetId(xpdp.Value)
					Else
						value = xpdp.GetType.InvokeMember("_value", Reflection.BindingFlags.GetField Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance, Nothing, xpdp, Nothing)
						If value Is Nothing Then
							value = DefaultValue
						End If
					End If
					Return value
				End If
			End If
		End If
		Return DefaultValue
	Catch ex As Exception
		Debug.WriteLine("BaseObject.GetKey Failed: " & ex.Message)
		Return DefaultValue
	End Try
End Function

So what does that do exactly?

  1. mi stores the MemberInfo for the “delayed” property we want to obtain the key from
  2. GetCustomPropertyValue is a MethodInfo of a internal method that allows to get the value from the CustomProperty store of the Persistent object that XPO uses
  3. Invoke the GetCustomPropertyValue method using parsing mi (MemberInfo) of our delayed property which if the Property has been marked with DelayedAttribute should return a XPDelayedProperty object
  4. We then check if the XPDelayedProperty object has been loaded in other words, has XPO already hit the database for the object, if so we can return the Id straight from this object, otherwise we invoke to obtain the value of “_value” which is what XPO uses to store the FK BEFORE it hits the DB for the actual record.

Example use of this in a object

        
        
        
        
        Public Property Sale() As Sale
            Get
                Return GetDelayedPropertyValue(Of Sale)(Fields.Sale)
            End Get
            Set(ByVal value As Sale)
                SetDelayedPropertyValue(Of Sale)(Fields.Sale, value)
                If IsReady() Then
                    If Sale Is Nothing Then
                        Client = Nothing
                    Else
                        Client = Sale.Client
                    End If
                End If
            End Set
        End Property

        
        Public Property SaleId As Nullable(Of Integer)
            Get
                Return GetRawId(Of Nullable(Of Integer))(Fields.Sale)
            End Get
            Set(ByVal value As Nullable(Of Integer))
                If IsReady() Then
                    If value.HasValue Then
                        Sale = Session.GetObjectByKey(Of Sale)(value.Value)
                    Else
                        Sale = Nothing
                    End If
                End If
            End Set
        End Property


In this example we have an object (lets call it an Agreement) that has an Association to a Sale. Within the Agreement we also have an Association with a PurchaseOrder record. Now say I open the PurchaseOrder in a "detail" window, and have an editable grid setup with the list of Associated Agreements and in that list I want to show what SaleId they relate to (as my users understand and deal with Sale ID’s all the time), I can in this case have a column showing SaleId instead of Sale.Id. This reduces the amount of data returned from the database by over 200%.

So is this great? no unfortunately it only half solves the problem

Whats the problem now?

Unfortunately when XPO grabs the Blog record object, as the CreatedBy property is marked as Delayed, XPO doesn’t obtain the FK. So if we use the GetRawId method you will still be hitting the DB to obtain the FK value (at least this is still smaller than having to obtain the entire User record).

Ultimately we need XPO to ascertain if a Delayed property is an association, if it is an association to grab the FK in the first hit to the database in preparation to downloading the related object.

What would be better yet, is for XPO to have a built in way to obtain the FK of an association instead of the associated object at all times.

What to do?

This problem has affected me since last year (when the blog posts stopped) and it took a while for me to get around to getting back to publishing my findings and also asking DX for official support for this scenario. So with that in mind, I present this blog and this suggestion

http://www.devexpress.com/Support/Center/p/S137254.aspx

Cheers

Comments  

 
0 # Michael Proctor [Dx-Squad] 2011-05-10 15:32 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

Latest Comments

My Twitter

Follow me on twitter