In .NET Web applications you can find that in many places custom classes and collections are better choices than the DataSet or DataTable.

The custom classes or the custom class collections, which are truly object oriented, allow developers to employ all object-oriented programming techniques.

Unfortunately, .NET Framework and Visual Studio 2005 don’t provide good support of the custom class. As a result, developers often encounter the following three barriers around using the custom class.

DataSet and DataTable are Microsoft’s solutions to these problems. Microsoft provides lots of built-in support of DataSet and DataTable in their development tools and in the .NET Framework library. But the DataSet and DataTable are far from object-oriented. Besides, they have lots of overhead.
  1. Data access. Though Microsoft makes data access with ADO.NET very straightforward and easy to use, developers still have to spend a considerable amount of time to develop the code connecting a custom class to the database. If you take a look at the Pet Shop reference project and the famous CSLA.NET framework you will find that this is never an easy job. The major difficulties come from filling the custom class with data from DataReader and building the SqlParameters, and they prevent further encapsulation of the data access into a truly object-oriented component.
  2. Data binding. Conceptually you can map a custom class or business object to a group of server controls in a Web Form. Two-way data binding and the ObjectDataSource introduced in ASP.NET 2.0 are designed to resolve this problem, but this approach has many restrictions and thus limits the developers’ programmatic control of data source.
  3. Business object template. Here the template refers to the well-known template pattern (References, [1]), not the code generation template. It turns out that abstracting common functionalities into a business object template is very hard or even impossible for the custom class. Developers typically use code generation as a remedy to this problem, but code generation may be a big concern for later upgrade and maintenance as functionalities and code size of the application increases.

The DataSet and DataTable are Microsoft’s solutions to these problems. Microsoft provides lots of built-in support of DataSet and DataTable in their development tools and in the .NET Framework library. But the DataSet and DataTable are far from object-oriented. Besides, they have lots of overhead.

Personally I think these three barriers are due to three more fundamental inadequacies in the .NET Framework:

  • Type mismatch between SQL and .NET Framework. There is no strict 1:1 mapping between the SQL data types and the .NET data types. When you convert a SQL data type to a .NET data type, the data loses its SQL type information. This leads to the big problem with custom class data access.
  • Run-time identity loss of the custom class property. In other words, the business object does not support getting or setting the value of its properties through the property name. In contrast, you can identify data in SQL and the Web Form through its identity (table column name and control unique ID). You can use reflection to remedy this problem but you’ll sacrifice some performance.
  • Lack of a common interface for the .NET data types and objects. A common interface should contain a common set of properties and methods. For example, the data types in the Web application require the support of nullability, state tracking, string formatting and parsing, strong typing, etc. System.Object is too general to meet this requirement. In terms of composite design pattern (References, [2]), this can be a very serious problem.

In this article I will present a solution to complement these three fundamental inadequacies. Consequently, the solution can completely overcome the barriers around the custom class data access and data binding. My solution also allows you to easily abstract common functionalities into business object templates.

The solution consists of two common interfaces called xType and xObject for the data type and the custom class respectively. xType always keeps its SQL type information. xObject supports the manipulation of the properties of the custom class through their names. The common set of properties and methods for xType include strong typing, nullability, state tracking, string formatting and parsing, clone, etc. xObject includes two common operations: retrieve the custom class property through its name and resolve the real name of the property. I’ll refer the real name to the corresponding table column name in the database. These two operations suffice to fix the second inadequacy above. You can augment xObject to include other common operations such as iteration, serialization, and so on. Before I jump to the detailed implementation of the common interfaces, here’s an early look at how xType and xObject can overcome the three barriers.

  • Data access. Reduce filling the custom class with data from DataReader and building the SqlParameters to one line of code as shown below. This means that you can build a truly object-oriented data access component to support the custom class:
void FillBizObject(xObject obj, IDataReader dr);
    
IList<xObject> FillBizObjectCollection(xObject
objSeed, IDataReader dr);
    
SqlParameter[] BuildSqlInputParameters(xObject
obj, params string[] objPropertyNames);
    
SqlParameter[] BuildSqlOutputParameters(xObject
obj, params string[] objPropertyNames);

  • Data binding. Establish the mapping between a group of Web Form fields and a custom class with one line of code, but we need to provide all related server controls a common interface with respect to data binding. These controls are called xControl in this article:
static void BindDataFromBizObjectToWeb(xObject
obj, params xControls[] arrxControls);
    
static void BindDataFromWebToBizObject(xObject
obj, params xControls[] arrxControls);
    
static void BindDataFromBizObjectToWeb(xObject
obj, Control container, params string[]
arrControlIDs);
    
static void BindDataFromWebToBizObject(xObject
obj, Control container, params string[]
arrControlIDs);
    

  • Create reusable business object templates. The common interfaces make it possible to abstract the common functionalities into the business object templates. The template classes do not only centralize your code, but they also allow you the flexibility to override the default functionalities with your own. In this article I will illustrate this flexibility by wrapping the Active Record and the Dynamic Object into the business object templates.

In the rest of the article I will introduce xType and xObject and their implementations. Then I will illustrate how to use xType and xObject to achieve the goals above. I will conclude with examining some representative code from the Microsoft .NET Pet Shop project (References, [6]) and discuss how you can use xType and xObject in practical Web applications. You will see the benefits that xType and xObject offer.

The Common Interface: xType and xObject

The common interface xType looks like the following:

public interface xType
{
    xType Clone();
    xType NewInstance();
        
    bool IsNull{ get; set;}
    void SetNull();
        
    object BizValue{ get;set;}
        
    bool ValueChanged { get;}
    void ResetChange();
  
    string ToString(string valIfNull,
                    string format);
    void Parse(string val, string valIfNull);
    
    SqlDbType SqlDbType { get;}
}

The solution can completely overcome the barriers around the custom class data access and data binding. It also allows you to easily abstract common functionalities into business object templates.
  • The xType interface above is far from complete yet. For example, you can incorporate many operations available in SqlTypes to xType. xType presents lots of desirable traits.
  • xType is an interface with a common set of operations for all subtypes. All subtypes such as xSqlInt32, xSqlBoolean, xSqlString, xSqlDateTime, and xImage will implement this interface.
  • All subtypes are strongly typed intrinsically. Though the property BizValue returns the generic type object, the data value is represented internally using a strongly typed variable, so you can safely cast object to specific types only if the data is not null. For convenience, all subtypes have a property called Value to get and set the internal value as strongly typed.
  • xType keeps its SQL type information through a property called SqlDbType. Since you can also deduce its .NET type from its SQL type, you can avoid using reflection to get the type information.
  • xType contains some of the most common operations, for example, nullability support, string formatting and parsing, state tracking, cloning, etc. Some essential operations such as equality checking and type conversion are not provided yet, but you can delegate them to the Value or BizValue property.
  • You could add another property called Name to xType. Name is related to how a business object names its data. I will not deal with this issue in this article.

SQL has 24 types. Accordingly you need to provide 24 corresponding subtypes in .NET as listed in Table 1. I’ve implemented the xTypes marked with a red asterisk in the sample code in this article. Since lots of SQL types map to the same .NET type, the workload is not as big as it looks.

The xObject common interface looks like:

public interface xObject
{
    xType GetValueByName(string realName);
    string ResolveRealName(string name);
}

In this article, xObject contains only two functions: ResolveRealName and GetValueByName. Function GetValueByName allows you to retrieve the property value through the real name and the returned type is xType. Since the identity of a variable can differ in SQL, a business object, and a Web Form, you can use the ResolveRealName function to get the real name of the property. I assume that the table column name of the data in the SQL code is the real name. There are many ways to implement function GetValueByName. In this article I use reflection to implement GetValueByName for simplicity. I also use a simple naming mechanism: the name of the property in the custom class and the control ID in the Web Form are the real name prefixed with a dash plus some other characters such as m_. Thus resolving the real name is just the removal of the prefix. You can also employ the property attributes and external XML configuration file to establish the name mapping, but this technique requires more coding.

Implementation of xTypes

Implementation of these derived types is fairly self-explanatory. I will only show two representative implementations in Listing 1: xInt32 and xImage. Note these three things:

  • You must ensure that Value is not null when you access the BizValue or Value properties; otherwise an exception will be thrown.
  • If the internal value is represented using reference type with the exception of string, the implementation of state change tracking will only track whether its internal reference has been changed.
  • If an operation does not make sense, either an exception will be thrown or a trivial message will be returned, for example, ToString for xImage will simply return a message.

For simplicity, all derived types inherit from an abstract class, xTypeBase, which provides partial implementation of xType.

Custom Class Data Access

The most troublesome issues in custom data access come from populating the custom class with data from DataReader into the custom class or the custom class collections and building the SqlParameters. xType and xObject allow you to wrap these functionalities into static functions. The following code snippet depicts how you can reduce populating the custom class with data from DataReader into the custom class or the custom class collections to one function call:

public static void FillBizObject(xObject xbo,
                                 SqlDataReader dr)
{
    int cnt = dr.FieldCount;
    for (int kk = 0; kk < cnt; kk++)
    {
        string realName = dr.GetName(kk);
        xbo.GetValueByName(realName).BizValue =
                             dr.GetValue(kk);
               
     }
}
    
public static xObject[] FillBizObjectCollection(
            xObject objectType, SqlDataReader dr)
{
    ArrayList arr = new ArrayList();
    while(dr.Read())
    {
       xObject xbo = objectType.NewInstance();
       FillBizObject(xbo, dr);
       arr.Add(xbo);
    }
    return (xObject[])arr.ToArray();
}

The function below shows how to build the SqlParameters. Note that the parameter names are passed in as the function parameter. The purpose of doing so is twofold: improve the code readability and inform the function what kind of custom class properties it is operating on. In later sections I will use this technique to deal with generic two-way data binding and create the business object templates:

public static SqlParameter[]
BuildSqlInputParameters(xObject xbo,
                        params string[] arrNames)
{
    SqlParameter[] arr =
                new SqlParameter[arrNames.Length];
    
    for(int kk=0; kk < arrNames.Length; kk++)
    {
       string realName =
                    ResolveRealName(arrNames[kk]);
       xType xt = xbo.GetValueByName(realName);
       arr[kk] = new SqlParameter("@" + realName,
                                  xt.BizValue);
    }
    return arr;
}

In these functions, xObject serves as the data container passing data back and forth. This is the pattern called Data Transfer Object (References, [3]). Note that you may have other ways or even better ways to write these functions. Here I only want to illustrate how you can wrap these functionalities into a static function with xType and xObject.

With the two hardest problems resolved, you can write a truly object-oriented, data-access component. You can use these two functions to replace tens of lines of data-access-related code in real applications.

Generic Two-way Data Binding in ASP.NET

The basic functionality of a Web Form is to present the data from the data source for viewing and editing. From the perspective of object-oriented thinking, you can map all or part of the properties in a custom class to a group of Web Form server controls (form fields). Practically, because of nullability checking and string formatting and parsing, the mapping requires lots of coding to handle these issues. ASP.NET 2.0 comes with a declarative approach to bind data to and from different data sources, but this method is only available to some ASP.NET 2.0-specific server controls and cannot fully reflect the mapping between the custom class and the group of Web Form fields. The method I will implement can overcome this restriction and establish mapping between a business object and a group of related server controls in the Web Form through data-binding functions below:

static void
BindDataFromBizObjectToWeb(xObject
obj, params xControls[] arrxControls);
    
static void BindDataFromWebToBizObject(xObject
obj, params xControls[] arrxControls);
    
static void BindDataFromBizObjectToWeb(xObject
obj, Control container, params string[]
arrControlIDs);
    
static void BindDataFromWebToBizObject(xObject
obj, Control container, params string[]
arrControlIDs);

Implementation of this technique requires three steps:

Identify a list of controls where you can bind the data to the properties of a business object. Typically the controls participating in data binding include Literal, Label, TextBox, RadioButton, CheckBox, DropDownList, RadioButtonList, CheckBoxList, ListBox, HiddenField, et al. You can expand this list according to your needs.

Extend the list of the server controls with a common interface called xControl as shown below. All controls must implement the common interface xControl:

public interface xControl
{
 void BindDataFromBizObjectToControl(xObject xbo);
 void BindDataFromControlToBizObject(xObject xbo);
}

Add the extended properties to the controls. Different controls may have different requirements when participating in data binding. These requirements are reflected through the value of the extended properties. For example, when binding data from a business object to Literal, Label, and TextBox, you may need string formatting. For all controls, if data is null, you should specify the replacement value. For the list controls CheckBoxList, RadioButtonList and ListBox, you can map their items to a single property (single mapping) or a group of properties of Boolean type (multiple mapping). For single mapping, Control ID corresponds to the property name of the business object. For multiple mapping, each ListItem Value will map to a property of the custom class. If you want to use null instead of some string value when the data is posted back, NullIfValuePostBack can meet your needs, but very few of the controls will need this property. I’ve summarized all these requirements in Table 2. For purposes of clarification, I simply split the list control into two types of controls: single mapping and multiple mapping. Note that ID or ListItem Value may not be the same as the property name of the business object. A supportive function ResolveRealName will resolve the real name from ID or ListItem Value.

After you resolve these thorny issues, implementing xControl becomes a fairly easy job. Listing 2 depicts the sample implementation of xTextBox. Listing 3 gives the implementation of the four data-binding functions. It also demonstrates how important it is to define string conversion functions in xType. In Listing 4 I present a different approach to implement this generic two-way data binding.

A business object template is superior to the code generation template in that code generation results in multiple copies of the code template and thus is hard to maintain and scale the application.

For example, to use this technique in your application, consider two-way data binding with Repeater. RepeaterItem will act as a control container, and in the Repeater, the data-binding event handler code looks like the following:

BindDataFromBizObjectToWeb((xObject)dataItem,
    repeaterItem, "txt_FirstName", "txt_LastName",
   "ddl_StateID", ......);

You can also take advantage of declarative syntax in ASPX code. Any public properties in a Control can be assigned value using markup code. The following code illustrates this usage:

<ccl:TextBoxEx id="txt_BirthDate"
                  Runat="Server"
                  FormatString="{0:d}"
                  ReplacementIfNull="">
</ccl:TextBox>

Creating Reusable Business Object Templates

I often encounter some common functionalities in the business object. For purposes of reusability, I want to abstract these common functionalities into a business object template. For example, I often encounter data patterns (Active Record, data mappers, etc), iteration, and XML serialization in data-driven Web applications. The current solution for developers is to create code-generation templates and use code-generation tools such as CodeSmith to generate the custom class as needed. But with xType and xObject, you can easily abstract these data patterns into the base template classes with default implementations. In addition, you can augment these two interfaces to support other functionalities such as iteration, XML serialization, object factory, events, etc.

A business object template is superior to the code generation template in that code generation results in multiple copies of the code template and thus is hard to maintain and scale the application.

In this article I will illustrate how to create the business object template by abstracting the Active Record and Dynamic Object into template classes.

Active Record

Active Record is an extremely useful enterprise application pattern (References, [5]). Typically developers use it to mimic a table record with four basic data access operations: Create, Retrieve, Update, and Delete, commonly known as CRUD. Listing 4 depicts the template class called ActiveRecordBase for the Active Record pattern. One feature that makes this implementation distinct from others is that it can update only changed fields by building a dynamically updated SQL statement, showcasing state tracking in xType. You can choose SQL text or a stored procedure to accomplish CRUD. However, dynamic updating only works with SQL text. I assume that the database table uses an identity column as its primary keys. For simplicity, I only show two static supportive functions: BuildTableUpdateString and UpdateDataRecord. I use BuildTableUpdateString to build dynamic SQL update text and UpdateDataRecord to execute the updated SQL text. In ActiveRecordBase, you will find a sample implementation of ResolveRealName and GetValueByName. To create an Active Record, you only need to extend ActiveRecordBase and supply all class properties, and then an Active Record is ready for use.

Dynamic Business Object

When developing Web applications using the custom class, I often find that I need a way to quickly create and instantiate a custom class object, which I refer to as a dynamic object. A Hashtable or ArrayList can serve this purpose. The property name and type information are passed in through the constructor of the dynamic object. A dynamic object implemented with xType has two advantages: the data is internally strongly typed all the time and the number of properties may vary. You can also create a dynamic object according to some external configuration setting. You can find applications of the dynamic object in the places where performance is not a critical issue, for example, your Web Forms dealing with application configuration settings. The following code snippet illustrates two types of dynamic objects: one has a varying number of properties and the other does not:

public abstract class VaringDynamicObjectBase :
 xObject
{
   Hashtable hashTable = new Hashtable();
    
   public xType GetValueByName(string realName)
   {
      return (xType) hashTable[realName];
   }
        
   public string ResolveRealName(string name)
   {
      return name;
   }
}
    
public abstract class QuickDynamicObjectBase :
 xObject
{
   Hashtable hashTable = new Hashtable();
    
   public xType GetValueByName(string realName)
   {
      return (xType)hashTable[realName];
   }
    
   public string ResolveRealName(string name)
   {
      return name;
   }
        
   public QuickDynamicObjectBase(
string[] propNames, SqlDbType[] propTypes)
   {
      for(int kk = 0; kk< propTypes.Length; kk++)
      {
          hashTable.Add(propNames[kk],
    xTypeHelp.CreatexTypeInstance(propTypes[kk]));
      }
   }
}

Use the Techniques in Your Application

With the two hardest problems resolved, you can write a truly object-oriented, data-access component. You can use these two functions to replace tens of lines of data-access-related code in real applications.

You can easily adapt xType, xObject, and all techniques in this article to your applications. What you are required to do is to change a data type to a specific xType and have your business object inherit from xObject or the business object template. Then you can enjoy the neat SqlParameter building, populating a data business object or business object collection from SQL, active record, dynamic object, and generic two-way data binding. The implementation of xObject in the article uses reflection and is not optimal. You can replace them with your own. The only inconvenience is that accessing the value of a business object property value needs a little more typing.

To better understand the advantages of xType and xObject, I’ll look at some code in the real application. Listing 6 shows some code excerpted from the Pet Shop project. Instead of modifying the code, I will point out how you can simplify the code with the use of xType and xObject.

Listing 5 depicts two functions: Insert and GetOrder excerpted from the Order.cs in the SQLServerDAL project of the Pet Shop. Look at the bulky code dealing with creating SQL parameters and populating data into the custom class. If you change the data type in the custom classes OrderInfo and AddressInfo to xType in the Model project, obviously you can reduce the tens of lines of code into a couple lines of code. You can go further to inherit the custom classes from the active record. Note that Pet Shop emphasizes using the stored procedures. It is not hard to create a stored-procedure based, active-record template. However, this requires you to restructure the code a little bit. I believe the code size will be further drastically reduced because of the built-in support of CRUD in the active record.

Next, look at the data-binding code in the user control AddressForm.ascx.cs in Listing 6. Actually you can replace the chunk of code with two data-binding function calls. You need to use xControls instead of those plain server controls.

Conclusion

At this point you probably get the concept of xType and xObject. In this article I can not talk about all issues around using xType and xObject. But you can see how you will benefit from them from the Pet Shop project In a word, xType and xObject are deliberately contrived to overcome the barriers resulted from SQL and .NET type mismatch and business object run-time identity lost. They connect the business object to the underlying data access, Web Form data binding, and application-level design patterns. You can use them to create reusable business object templates and avoid using or lessen the dependency on code generation. xType can make your application more compact, more readable, and easy to change and maintain.

References

[1] Gamma, Erich, Richard Helm, Ralph Johhnson, and John Vlissides. 1995. Design Patterns: Elements of Resuable Object-oriented Software, P325.

[2] Gamma, Erich, Richard Helm, Ralph Johhnson, and John Vlissides. 1995. Design Patterns: Elements of Resuable Object-oriented Software, P163

[3] Martin Flower. 2002. Patterns of Enterprise Application Architecture, P486

[4] Bill Xie. 2006. “Two-way Databinding”, http://www.asptoday.com/Content.aspx?id=2437

[5] Martin Flower. 2002. Patterns of Enterprise Application Architecture, P160

[6] Microsoft .NET Pet Shop 4. http://msdn2.microsoft.com/en-us/library/aa479070.aspx