Wednesday, August 28, 2013

Monday, December 14, 2009

The On Demand Global Workforce - oDesk


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.

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 List FillCollection(IDataReader dr);
        public static IList FillCollection(IDataReader dr, ref IList objToFill);
        public static List FillCollection(IDataReader dr, ref int totalRecords);
        public static ArrayList FillCollection(IDataReader dr, Type objType);
        public static IList FillCollection(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 IDictionary FillDictionary(IDataReader dr) where TItem : DotNetNuke.Entities.Modules.IHydratable;
        public static IDictionary FillDictionary(IDataReader dr, ref IDictionary objToFill) where TItem : DotNetNuke.Entities.Modules.IHydratable;
        public static Dictionary FillDictionary(string keyField, IDataReader dr);
        public static Dictionary FillDictionary(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 SortedList FillSortedList(string keyField, IDataReader dr);
        public static TObject GetCachedObject(CacheItemArgs cacheItemArgs, CacheItemExpiredCallback cacheItemExpired);
        public static Dictionary GetProperties();
        public static Dictionary GetProperties(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 List GetPracticeType()

        {
            return CBO.FillCollection(DataProvider.Instance().GetPracticeByType());

        }


        public List GetSpecialty()

        {
            return CBO.FillCollection(DataProvider.Instance().GetSpecialty());

        }