Wednesday, September 08, 2010

DNN Dev Zone Library Minimize
Last Updated On: 10/28/2006 3:20:37 PM
ArticlesSkip Navigation Links.  

General DotNetNuke Development

Hydrating your Business Objects

Author: Charles Nurse
Posted: Monday, September 11, 2006
Last Updated: Saturday, October 28, 2006

Abstract: This article provides background information about the CBO class used in DotNetNuke, including when to use the class, and more importantly when not to use it. It also provides an example of a custom hydrator.

Introduction

In a recent Blog on DotNetNuke.com I described the results of some profiling tests I had carried out. In the blog I discussed the CBO class, when to use it and more importantly, when not to use it. In this article I will expand on that discussion and provide an example of a custom hydrator.

The CBO Class

The CBO class is a utility class in the DotNetNuke Library that performs Business Object hydration. Hydration is a term that refers to the filling of an instance of a class, in this case from a DataReader. CBO exposes both standard and generic versions of two methods; FillObject (which is used to hydrate a single object) and FillCollection (which is used to hydrate a collection of objects).

These methods take the type of the object to hydrate as a parameter and use reflection to determine how to hydrate the object. As discussed in the blog entry, reflection is an expensive process and could be replaced by writing custom hydrators. Let's first examine the process to hydrate a single object instance in the Links module (LinkController.GetLink()) using the CBO class. Later in this article we will describe how to use a custom hydrator that avoids the use of reflection thus improving performance.

Listing 1: The GetLink method in the LinkController Class
   1:  Public Function GetLink(ByVal ItemID As Integer, ByVal ModuleId As Integer) As LinkInfo
   2:      Return CType(CBO.FillObject(DataProvider.Instance().GetLink(ItemID, ModuleId),_
GetType(LinkInfo)), LinkInfo)
   3:  End Function

Listing 1 shows the GetLink method in the LinkController class. This method is fairly straightforward, as it calls a similarly named method in the DataProvider, which returns a DataReader containing a single link identified by the ItemId and Module Id. This DataReader is then passed to the FillObject method together with the type of object to fill (LinkInfo in this case).

The FillObject method

The FillObject method of the CBO class called above is a static (or shared) method that takes a DataReader and a type as parameters and returns an object. This method has an override that also takes a Boolean property - ManageDataReader - that determines whether to close the DataReader after the object has been created (see Listing 2).

Listing 2: The FillObject method
   1:  Public Shared Function FillObject(ByVal dr As IDataReader, ByVal objType As Type, _
   2:          ByVal ManageDataReader As Boolean) As Object
   3:   
   4:      Dim objFillObject As Object
   5:   
   6:      ' get properties for type
   7:      Dim objProperties As ArrayList = GetPropertyInfo(objType)
   8:   
   9:      ' get ordinal positions in datareader
  10:      Dim arrOrdinals As Integer() = GetOrdinals(objProperties, dr)
  11:   
  12:      Dim [Continue] As Boolean
  13:      If ManageDataReader Then
  14:          [Continue] = False
  15:          ' read datareader
  16:          If dr.Read() Then
  17:              [Continue] = True
  18:          End If
  19:      Else
  20:          [Continue] = True
  21:      End If
  22:   
  23:      If [Continue] Then
  24:          ' create custom business object
  25:          objFillObject = CreateObject(objType, dr, objProperties, arrOrdinals)
  26:      Else
  27:          objFillObject = Nothing
  28:      End If
  29:   
  30:      If ManageDataReader Then
  31:          ' close datareader
  32:          If Not dr Is Nothing Then
  33:              dr.Close()
  34:          End If
  35:      End If
  36:   
  37:      Return objFillObject
  38:  End Function

This override is used so that the FillCollection methods that also take a DataReader can use the same method to fill each object. The FillCollection methods call this override with ManageDataReader set to false, so they can continue to use the DataReader, while the FillObject method that GetLink calls, calls this override with ManageDataReader set to true, as it only needs to process one record. This strategy ensures that the DataReader is closed once it is finished with.

There are two methods of importance that are called by FillObject. At the beginning of the method FillObject calls GetPropertyInfo to get a list of the properties for the current type (LinkInfo). This method (see Listing 3) uses the cache to save the list of properties as the TypeInfo.GetProperties() method call is expensive.

Later the FillObject method calls the private method CreateObject to create an instance of LinkInfo and to hydrate it form the DataReader.

The CreateObject method

The CreateObject method is shown in Listing 4. In line 9 the object is instantiated using Activator.CreateInstance(objType). This is a much more expensive way to create a new instance of an object than using New. Similarly in lines 19, 23, 33, 37, 43 and 48 the new instance's properties are set using a PropertyInfo.SetValue() method. This also is much more expensive than using a property setter.

Although the call to GetPropertyInfo uses the cache to save the list of properties, this is only part of the problem with reflection. The properties are the same for every instance and can be cached. However, we cannot cache anything during our creation of a new instance of the object, and these calls are very expensive. Next I will describe how you can write your own hydration method to avoid using the CreateObject method.

Creating a Custom Hydration Method

Creating your own hydration mehod is actually very easy. After all you should know everything about the object you are hydrating. As an example lets look at a FillLinkInfo method which could be used in the LinkController to replace the call to CBO.FillObject(). This method is shown in Listing 5.

Listing 5: The FillLinkInfo custom hydration method
   1:  Private Function FillLinkInfo(ByVal dr As IDataReader, _
   2:              ByVal CheckForOpenDataReader As Boolean) As LinkInfo
   3:      Dim obLinkInfo As New LinkInfo
   4:   
   5:      ' read datareader
   6:      Dim canContinue As Boolean = True
   7:      If CheckForOpenDataReader Then
   8:          canContinue = False
   9:          If dr.Read Then
  10:              canContinue = True
  11:          End If
  12:      End If
  13:   
  14:      If canContinue Then
  15:          obLinkInfo.ItemId = Convert.ToInt32(Null.SetNull(dr("ItemID"), _
  16:              obLinkInfo.ItemId))
  17:          obLinkInfo.ModuleId = Convert.ToInt32(Null.SetNull(dr("ModuleID"), _
  18:              obLinkInfo.ModuleId))
  19:          obLinkInfo.Title = Convert.ToString(Null.SetNull(dr("Title"), _
  20:              obLinkInfo.Title))
  21:          obLinkInfo.Url = Convert.ToString(Null.SetNull(dr("Url"), _
  22:              obLinkInfo.Url))
  23:          obLinkInfo.ViewOrder = Convert.ToInt32(Null.SetNull(dr("Vieworder"), _
  24:              obLinkInfo.ViewOrder))
  25:          obLinkInfo.Description = Convert.ToString(Null.SetNull(dr("Description"), _
  26:              obLinkInfo.Description))
  27:          obLinkInfo.CreatedByUser = Convert.ToInt32(Null.SetNull(dr("CreatedByUser"), _
  28:              obLinkInfo.CreatedByUser))
  29:          obLinkInfo.CreatedDate = Convert.ToDateTime(Null.SetNull(dr("CreatedDate"), _
  30:              obLinkInfo.CreatedDate))
  31:          obLinkInfo.TrackClicks = Convert.ToBoolean(Null.SetNull(dr("TrackClicks"), _
  32:              obLinkInfo.TrackClicks))
  33:          obLinkInfo.NewWindow = Convert.ToBoolean(Null.SetNull(dr("NewWindow"), _
  34:              obLinkInfo.NewWindow))
  35:      End If
  36:   
  37:      Return obLinkInfo
  38:  End Function

Note that this method is pretty straightforward. The very first statement creates a new LinkInfo object instance. The next block of code determines whether to check for an open DataReader. This parameter would be passed in as true if we are just filling a single object, or false if we are filling a collection. It works in a similar way to the ManageDataReader parameter in the CBO class.

The last block, which only runs if there is a current record in the DataReader, fills all the properties of the LinkInfo from the corresponding field in the DataReader. The last line returns the hydrated LinkInfo object.

In order to use this method instead of the CBO method, the GetLink method (shown in Listing 1) needs to be modified as shown in Listing 6.

Listing 6: The GetLink method of the LinkController class, updated to use our new custom hydration method
   1:  Public Function GetLink(ByVal ItemID As Integer, ByVal ModuleId As Integer) As LinkInfo
   2:      Dim objLinkInfo As LinkInfo
   3:   
   4:      Dim dr As IDataReader = DataProvider.Instance().GetLink(ItemID, ModuleId)
   5:      Try
   6:          objLinkInfo = FillLinkInfo(dr, True)
   7:      Finally
   8:          If Not dr Is Nothing Then
   9:              dr.Close()
  10:          End If
  11:      End Try
  12:   
  13:      Return objLinkInfo
  14:  End Function

Its not that difficult to build your own hydration method, and the gain in performance is, in most cases, well worth it. The CBO methods should be treated as a convenient service to be used sparingly. In general if a class is being hydrated on a regular basis you should create your own custom hydrator. If it is only being filled rarely, then feel free to use the CBO methods.

Conclusion

This article describes the DotNetNuke CBO class, when to use it and when not to use it. It also provides an example of a custom hydration method to hydrate a LinkInfo object.


About the Author: Charles Nurse is the owner of Keydance Computer Services, the sponsor of this site, as well as being a Trustee and Lead Developer in the DotNetNuke project.


     
A Keydance Web Portal | Copyright 2006 by Keydance Computer Services | Privacy Statement | Terms Of Use