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 |
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 |
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 |
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 |
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.CBO Class overview
namespace DotNetNuke.Common.Utilities
{
public class CBO
{
public CBO();
public static object CloneObject(object objObject);
public static void CloseDataReader(IDataReader dr, bool closeReader);
public static TObject CreateObject();
public static TObject CreateObject(bool initialise);
public static object CreateObject(Type objType, bool initialise);
public static TObject DeserializeObject(Stream stream);
public static TObject DeserializeObject(string fileName);
public static TObject DeserializeObject(TextReader reader);
public static TObject DeserializeObject(XmlDocument document);
public static TObject DeserializeObject(XmlReader reader);
public static ListFillCollection (IDataReader dr);
public static IListFillCollection (IDataReader dr, ref IList objToFill);
public static ListFillCollection (IDataReader dr, ref int totalRecords);
public static ArrayList FillCollection(IDataReader dr, Type objType);
public static IListFillCollection (IDataReader dr, IList objToFill, bool closeReader);
public static ArrayList FillCollection(IDataReader dr, ref Type objType, ref int totalRecords);
public static IList FillCollection(IDataReader dr, Type objType, ref IList objToFill);
public static IDictionaryFillDictionary (IDataReader dr) where TItem : DotNetNuke.Entities.Modules.IHydratable;
public static IDictionaryFillDictionary (IDataReader dr, ref IDictionary objToFill) where TItem : DotNetNuke.Entities.Modules.IHydratable;
public static DictionaryFillDictionary (string keyField, IDataReader dr);
public static DictionaryFillDictionary (string keyField, IDataReader dr, IDictionary objDictionary);
public static TObject FillObject(IDataReader dr);
public static TObject FillObject(IDataReader dr, bool closeReader);
public static object FillObject(IDataReader dr, Type objType);
public static object FillObject(IDataReader dr, Type objType, bool closeReader);
public static SortedListFillSortedList (string keyField, IDataReader dr);
public static TObject GetCachedObject(CacheItemArgs cacheItemArgs, CacheItemExpiredCallback cacheItemExpired);
public static DictionaryGetProperties ();
public static DictionaryGetProperties(Type objType);
[Obsolete("Obsolete in DotNetNuke 5.0. Replaced by GetProperties(Of TObject)() ")]
public static ArrayList GetPropertyInfo(Type objType);
public static void InitializeObject(object objObject);
public static object InitializeObject(object objObject, Type objType);
[Obsolete("Obsolete in DotNetNuke 5.0. Replaced by SerializeObject(Object) ")]
public static XmlDocument Serialize(object objObject);
public static void SerializeObject(object objObject, Stream stream);
public static void SerializeObject(object objObject, string fileName);
public static void SerializeObject(object objObject, TextWriter textWriter);
public static void SerializeObject(object objObject, XmlDocument document);
public static void SerializeObject(object objObject, XmlWriter writer);
}
}
How to use CBO class practically to your project
public ListGetPracticeType()
{
return CBO.FillCollection(DataProvider.Instance().GetPracticeByType());
}
public ListGetSpecialty()
{
return CBO.FillCollection(DataProvider.Instance().GetSpecialty());
}