This article is based on material originally published on DNN Developer Zone (www.dnndevzone.com).  This version has been updated to cover the changes made in DotNetNuke 5.0.

Introduction

In a Blog a few months agoon DotNetNuke.com I described the results of some profiling tests I had carried out. In that blog I discussed the CBO class, when to use it and more importantly, when not to use it.  Since writing that Blog post there have been some changes to how the CBO class works.  This article will focus on how the CBO class works and how it can be used - in a future article I will describe the IHydratable Interface, and how it can be used to improve performance.

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), as well as multiple versions of a generic FillDictionary method.

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).

Using the FillObject method to hydrate a single object

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 - closeReader- that determines whether to close the DataReader after the object has been created (see Listing 2).

Listing 2: The FillObject methods
   1:  Public Shared Function FillObject(ByVal dr As IDataReader, ByVal objType As Type) As Object
   2:      Return CreateObjectFromReader(objType, dr, True)
   3:  End Function
   4:   
   5:  Public Shared Function FillObject(ByVal dr As IDataReader, ByVal objType As Type, 
                                         ByVal closeReader As Boolean) As Object
   6:      Return CreateObjectFromReader(objType, dr, closeReader)
   7:  End Function

This override is used for situations when the the caller needs to continue to use the IDataReader. An example of this would be when Data Layer returns two or more sets of data, one is used to hydrate one set of objects and the other is used for some other reason - perhaps to hydrate a second set of objects.  Both overrides call the private method CreateObjectFromReader (Listing 3)

Listing 3: CreateObjectFromReader
   1:  Private Shared Function CreateObjectFromReader(ByVal objType As Type, ByVal dr As IDataReader, 
                                                      ByVal closeReader As Boolean) As Object
   2:      Dim objObject As Object = Nothing
   3:   
   4:      Dim canRead As Boolean = True
   5:      If closeReader Then
   6:          canRead = False
   7:          ' read datareader
   8:          If dr.Read() Then
   9:              canRead = True
  10:          End If
  11:      End If
  12:   
  13:      If canRead Then
  14:          'Create the Object
  15:          objObject = CreateObject(objType, False)
  16:   
  17:          'hydrate the custom business object
  18:          FillObjectFromReader(objObject, dr)
  19:      End If
  20:   
  21:      ' Ensure DataReader is closed
  22:      CloseDataReader(dr, closeReader)
  23:   
  24:      Return objObject
  25:  End Function

The first part of this method (lines 4-11) determine if there is a record to read.  Next in line 15 we create an object of the correct type and in line 18 we call FillObjectFromReader to hydrate the object from the DataReader.

Before we look in detail at the FillObjectFromReader method lets examine how the FillCollection methods work.

Using the FillCollection method to hydrate a collection of objects

The public FillCollection methods behave similarly.  In the Links module, for example, the LinksController class has a method to return all the Links

Listing 4: The GetLinks method in the LinkController Class
   1:  Public Function GetLinks(ByVal ModuleId As Integer) As ArrayList
   2:      Return CBO.FillCollection(DataProvider.Instance().GetLinks(ModuleId), GetType(LinkInfo))
   3:  End Function

This method calls the FillCollection method, passing, as before, the DataReader and the type of the object to hydrate.

Listing 5: The FillCollection method
   1:  Public Shared Function FillCollection(ByVal dr As IDataReader, ByVal objType As Type) As ArrayList
   2:      Return DirectCast(FillListFromReader(objType, dr, New ArrayList(), True), ArrayList)
   3:  End Function

As with the case of the FillObject method the FillCollection method calls a private FillListFromReader method, which fills the collection passed to it with objects created from the DataReader (see Listing 6). 

Listing 6 The FillListFromReader method
   1:  Private Shared Function FillListFromReader(ByVal objType As Type, ByVal dr As IDataReader, 
                                  ByVal objList As IList, ByVal closeReader As Boolean) As IList
   2:      Dim objObject As Object
   3:   
   4:      ' iterate datareader
   5:      While dr.Read
   6:          'Create the Object
   7:          objObject = CreateObjectFromReader(objType, dr, False)
   8:   
   9:          ' add to collection
  10:          objList.Add(objObject)
  11:      End While
  12:   
  13:      ' Ensure DataReader is closed
  14:      CloseDataReader(dr, closeReader)
  15:   
  16:      Return objList
  17:  End Function

The important thing to note being that the FillListFromReader method calls the same CreateObjectFromReader method that is called by FillObject, with the closeReader parameter set to false.

The FillObjectFromReader method

As we have discussed above all calls to FillObject, FillCollection (and FillDictionary) are routed through the private method FillObjectFromDataReader (Listing 7)

Listing 7: The FillObjectFromReader method
   1:  Private Shared Sub FillObjectFromReader(ByVal objObject As Object, ByVal dr As IDataReader)
   2:      'Determine if object is IHydratable
   3:      If TypeOf objObject Is IHydratable Then
   4:          'Use IHydratable's Fill
   5:          Dim objHydratable As IHydratable = TryCast(objObject, IHydratable)
   6:          If objHydratable IsNot Nothing Then
   7:              objHydratable.Fill(dr)
   8:          End If
   9:      Else
  10:          'Use Reflection
  11:          HydrateObject(objObject, dr)
  12:      End If
  13:  End Sub

This method determines if the object implements the IHydratable interface (I will discus this interface in more detail in a future blog).  If it does it calls the objects Fill method, if not it calls the HydrateObject method which uses Reflection to hydrate the object (see Listing 8)

Listing 8: The HydrateObject method
   1:  Private Shared Sub HydrateObject(ByVal objObject As Object, ByVal dr As IDataReader)
   2:      Dim objPropertyInfo As PropertyInfo = Nothing
   3:      Dim objPropertyType As Type = Nothing
   4:      Dim objDataValue As Object
   5:      Dim objDataType As Type
   6:      Dim intIndex As Integer
   7:   
   8:      ' get cached object mapping for type
   9:      Dim objMappingInfo As ObjectMappingInfo = GetObjectMapping(objObject.GetType)
  10:   
  11:      ' fill object with values from datareader
  12:      For intIndex = 0 To dr.FieldCount - 1
  13:          'If the Column matches a Property in the Object Map's PropertyInfo Dictionary
  14:          If objMappingInfo.Properties.TryGetValue(dr.GetName(intIndex).ToUpperInvariant, 
objPropertyInfo) Then
  15:              'Get its type
  16:              objPropertyType = objPropertyInfo.PropertyType
  17:   
  18:              'If property can be set
  19:              If objPropertyInfo.CanWrite Then
  20:                  'Get the Data Value from the data reader
  21:                  objDataValue = dr.GetValue(intIndex)
  22:   
  23:                  'Get the Data Value's type
  24:                  objDataType = objDataValue.GetType
  25:   
  26:                  If IsDBNull(objDataValue) Then
  27:                      ' set property value to Null
  28:                      objPropertyInfo.SetValue(objObject, Null.SetNull(objPropertyInfo), Nothing)
  29:                  ElseIf objPropertyType.Equals(objDataType) Then
  30:                      'Property and data objects are the same type
  31:                      objPropertyInfo.SetValue(objObject, objDataValue, Nothing)
  32:                  Else
  33:                      ' business object info class member data type does not match  
  34:                      ' datareader member data type
  35:                      Try
  36:                          'need to handle enumeration conversions differently than other 
  37:                          'base(types)
  38:                          If objPropertyType.BaseType.Equals(GetType(System.Enum)) Then
  39:                              ' check if value is numeric and if not convert to integer 
  40:                              ' ( supports databases like Oracle )
  41:                              If IsNumeric(objDataValue) Then
  42:                                  objPropertyInfo.SetValue(objObject, System.Enum.ToObject(
objPropertyType, Convert.ToInt32(objDataValue)), Nothing)
  43:                              Else
  44:                                  objPropertyInfo.SetValue(objObject, System.Enum.ToObject(
objPropertyType, objDataValue), Nothing)
  45:                              End If
  46:                          ElseIf objPropertyType.FullName.Equals("System.Guid") Then
  47:                              ' guid is not a datatype common across all databases 
  48:                              ' ( ie. Oracle )
  49:                              objPropertyInfo.SetValue(objObject, Convert.ChangeType(
                                           New Guid(objDataValue.ToString()), objPropertyType), Nothing)
  50:                          ElseIf objPropertyType.FullName.Equals("System.Version") Then
  51:                              objPropertyInfo.SetValue(objObject, 
                                           New Version(objDataValue.ToString()), Nothing)
  52:                          Else
  53:                              ' try explicit conversion
  54:                              objPropertyInfo.SetValue(objObject, objDataValue, Nothing)
  55:                          End If
  56:                      Catch
  57:                          objPropertyInfo.SetValue(objObject, Convert.ChangeType(objDataValue, 
                                                           objPropertyType), Nothing)
  58:                      End Try
  59:                  End If
  60:              End If
  61:          End If
  62:      Next
  63:  End Sub

This blog describes the overall approach to hydration in the DotNetNuke LIbrary.  In Part 2 I will describe the IHydratable Interface, and how you can improve the performance of your own modules.


Posted in: DotNetNuke  Tags:

Comments


trackback
Trackback from Smart-Thinker DotNetNuke Development Blog

DotNetNuke CodeSmith Templates with IHydratable


October 12. 2008 11:48
trackback
Trackback from iLinkShare (Web 2.0 linksharing)

Cbo Links

Comments are closed

 Search Blog

 Adsense

 Calendar

«  September 2010  »
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910
View posts in large calendar

 Tags

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010 Thoughts from the Wet Coast