Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Casting from a Collection to a Data Table using Generics and Attributes

0.00/5 (No votes)
22 Aug 2006 1  
This article shows how you can use Attributes to solve problems that involve cross cutting concerns.

Introduction

The following step by step example will demonstrate how you can use Aspect Oriented Programming (AOP) to cast a collection of objects into a DataTable. This particular example will leverage the power of attributes, generics, and reflection to explicitly convert a collection of container classes into a DataTable.

I should state for the record that this article was inspired in part by a cogent book on the topic: Applied .NET Attributes by Jason Bock and Tom Barnaby.

Step 1: Build your own Custom Attribute Class

Custom Attributes always inherit from System.Attribute, in fact any class that inherits from System.Attribute whether directly or indirectly is an attribute class. Attribute classes also follow the convention of having the word �Attribute� attached as a suffix to the class name.

Attributes allow you to add metadata to an object that you can read at run-time via reflection. As a result, they provide an elegant (and granular) solution to the object oriented problem of cross-cutting concerns.

Our first step will be to build a custom attribute class that will allow us to acquire meta data about the properties of our container class (which we haven�t built yet) at run-time. The beauty of this solution is that we can use our custom attribute class (in this case ConversionAttribute) to decorate any class that we decide to add to our project at a later date.

using System; 
namespace Coreweb.Example
{
   
[AttributeUsage(AttributeTargets.Property)]
   public class ConversionAttribute : Attribute
{
      private bool m_dataTableConversion;
      private bool m_allowDbNull;
      private bool m_keyField; 
            public ConversionAttribute() { } 
            public bool DataTableConversion
      {
         get { return m_dataTableConversion; }
         set { m_dataTableConversion = value; }
      } 
            public bool AllowDbNull
      {
         get { return m_allowDbNull; }
         set { m_allowDbNull = value; }
      } 
      public bool KeyField
      {
         get { return m_keyField; }
         set { m_keyField = value; }
      }
   }
}

Step 2: Build a Container Class

Now we create a container class! Notice how I've decorated the properties of this class with the attributes we�ve created in Step 1. In Step 4 we'll use this information to build a DataTable via reflection.

using System;using System.Collections; namespace Coreweb.Example
{
   public class Employee
   {
      private string m_firstName;
      private string m_lastName;
      private DateTime m_birthDate;
      private ArrayList m_aList; 

      public Employee(string firstName, string lastName, DateTime birthDate)
      {
         m_firstName = firstName;
         m_lastName = lastName;
         m_birthDate = birthDate;
      }

      [Conversion(DataTableConversion = true, KeyField = true, 
       AllowDbNull = false)]
      public string FirstName
      {
         get { return m_firstName; }
         set { m_firstName = value; }
      }

      [Conversion(DataTableConversion = true, KeyField = true, 
       AllowDbNull = false)]
      public string LastName
      {
         get { return m_lastName; }
         set { m_lastName = value; }
      } 

      [Conversion(DataTableConversion = true, AllowDbNull = false)]
      public int Age
      {
         get { return this.GetAge(); }
      } 

      [Conversion(DataTableConversion = true, AllowDbNull = true)]
      public DateTime BirthDate
      {
         get { return m_birthDate; }
         set { m_birthDate = value; }
      } 
      // I've included this property to demonstrate how properties that aren't

      // decorated are excluded from our explicit conversion. (Properties 

      // that hold reference types aren't good candidates for inclusion

      // in our DataTable.  If you try to bind this field to the DataGrid 

      // in the ConversionTestHarness you will get a 
// System.Web.HttpException)
public ArrayList AList { get { return m_aList; } set { m_aList = value; } } // This method derives the age of the Employee from his / her birth date. private int GetAge() { int years = DateTime.Now.Year - m_birthDate.Year; if (DateTime.Now.Month < m_birthDate.Month || (DateTime.Now.Month == m_birthDate.Month && DateTime.Now.Day < m_birthDate.Day)) { years--; } return years; } } }

Step 3: Create an Interface - IDataTableConverter

For purpose of this example we�ll be converting a generic list (System.Collections.Generic.List) to a DataTable. The implementation for a dictionary might be different so we�ll want to leverage the power of an interface to abstract away from any specific implementation.

using System;
using System.Collections.Generic;
using System.Data; 

namespace Coreweb.Example
{
   public interface IDataTableConverter<T>
   {
      DataTable GetDataTable(List<T> items);
   }
}

Step 4: Build a DataTableConverter Class

This is the class that will be doing all the work. Essentially, this class uses the System.Reflection namespace to query attributes at run-time, build a DataTable schema, and fill it.

using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection; 
 namespace Coreweb.Example
{
   public class DataTableConverter<T> : IDataTableConverter<T>
   {
      private bool m_enforceKeys; 
      public DataTableConverter() { } 
            public DataTableConverter(bool enforceKeys)
      {
         m_enforceKeys = enforceKeys;
      } 
            public DataTable GetDataTable(List<T> items)
      {
         DataTable dt; 
               try
         {
            // Build a table schema from the first element in the collection

            dt = this.ConstructDataTableSchema(items[0]);
         }
         catch (IndexOutOfRangeException ex)
         {
            throw (new ApplicationException(
                      "Cannot convert List of zero length to a " +
                      "DataTable", ex));
         } 
         // If the container is not convertable than throw an 

         // ApplicationException.

         if (dt != null)
         {
            // Create a new row for every item in the collection and fill it.
for (int i = 0; i < items.Count; i++)
{ DataRow dr = dt.NewRow(); Type type = items[i].GetType(); MemberInfo[] members = type.GetProperties(); foreach (MemberInfo member in members) { object[] attributes = member.GetCustomAttributes(true); if (attributes.Length != 0) { foreach (object attribute in attributes) { ConversionAttribute ca = attribute as ConversionAttribute; if (ca != null) { if (ca.DataTableConversion) { string[] nameArray
= member.Name.ToString().Split( Convert.ToChar(" ")); PropertyInfo prop = type.GetProperty(
nameArray[0]); Type valueType = prop.GetValue(items[i],
null).GetType(); dr[nameArray[0]] = prop.GetValue(items[i],null); } } } } } dt.Rows.Add(dr); } return dt; } else { throw new ApplicationException("List items are not convertable."); } } // This method reads the attributes of your container class via // reflection in order to build a schema for the DataTable that you
// will explicitly convert to.
private DataTable ConstructDataTableSchema(T item) { string tableName = string.Empty; List<DTCONVERTERCONTAINER> schemaContainers = new List<DTCONVERTERCONTAINER>(); Type type = item.GetType(); MemberInfo[] members = type.GetProperties(); foreach (MemberInfo member in members) { object[] attributes = member.GetCustomAttributes(true); if (attributes.Length != 0) { foreach (object attribute in attributes) { ConversionAttribute ca = attribute as ConversionAttribute; if (ca != null) { if (ca.DataTableConversion) { // The name of the container class is used to name
// your DataTable
string[] classNameArray =
member.ReflectedType.ToString().Split( Convert.ToChar(".")); tableName = classNameArray[classNameArray.Length- 1]; string name = member.Name.ToString(); PropertyInfo prop = type.GetProperty(name); Type valueType = prop.GetValue(item, null).GetType(); // Each property that is will be a column in our
// DataTable.
schemaContainers.Add(new DTConverterContainer(name, valueType, ca.AllowDbNull, ca.KeyField)); } } } } } if (schemaContainers.Count > 0) { DataTable dataTable = new DataTable(tableName); DataColumn[] dataColumn = new DataColumn[schemaContainers.Count]; // Counts the number of keys that will need to be created int totalNumberofKeys = 0; foreach (DTConverterContainer container in schemaContainers) { if (container.IsKey == true && m_enforceKeys == true) { totalNumberofKeys = totalNumberofKeys + 1; } } // Builds the DataColumns for our DataTable DataColumn[] keyColumnArray = new DataColumn[totalNumberofKeys]; int keyColumnIndex = 0; for (int i = 0; i < schemaContainers.Count; i++) { dataColumn[i] = new DataColumn(); dataColumn[i].DataType = schemaContainers[i].PropertyType; dataColumn[i].ColumnName = schemaContainers[i].PropertyName; dataColumn[i].AllowDBNull = schemaContainers[i].AllowDbNull; dataTable.Columns.Add(dataColumn[i]); if (schemaContainers[i].IsKey == true &&
m_enforceKeys == true) { keyColumnArray[keyColumnIndex] = dataColumn[i]; keyColumnIndex = keyColumnIndex + 1; } } if (m_enforceKeys) { dataTable.PrimaryKey = keyColumnArray; } return dataTable; } return null; } private class DTConverterContainer { private string m_propertyName; private Type m_propertyType; private bool m_allowDbNull; private bool m_isKey; internal DTConverterContainer(string propertyName, Type propertyType, bool allowDbNull, bool isKey) { m_propertyName = propertyName; m_propertyType = propertyType; m_allowDbNull = allowDbNull; m_isKey = isKey; } public string PropertyName { get { return m_propertyName; } set { m_propertyName = value; } } public Type PropertyType { get { return m_propertyType; } set { m_propertyType = value; } } public bool AllowDbNull { get { return m_allowDbNull; } set { m_allowDbNull = value; } } public bool IsKey { get { return m_isKey; } set { m_isKey = value; } } } } }

Step 5: Build your own Generic List

Here were we add the explicit conversion operator that will allow our list to be converted to a DataTable. If you want to learn more about type conversions in C# I recommend you read the following (excellent) article by Rajesh V.S.: Type Conversions.

using System;
using System.Collections.Generic;
using System.Data;

namespace Coreweb.Example
{
   public class CoreWebList<T> : List<T> 
   {
      private static bool m_enforceKeysInDataTableConversion;

      public CoreWebList() 
      {
         m_enforceKeysInDataTableConversion = false;
      }    
      
      public bool EnforceKeysInDataTableConversion
      {
         get { return m_enforceKeysInDataTableConversion; }
         set { m_enforceKeysInDataTableConversion = value; }
      }
 
      public static explicit operator DataTable(CoreWebList<T> list)
      {
         IDataTableConverter<T> converter = new DataTableConverter<T>(
            m_enforceKeysInDataTableConversion);

         return converter.GetDataTable(list);
      }
   }
}

Step 6: Build your test harness

In this step we're going to fill our collection with Employees, explicitly convert to a DataTable, and then bind to DataGrid.

<%@ Page Language="C#" AutoEventWireup="true" 
         CodeFile="ConversionTestHarness.aspx.cs"
         Inherits="ConversionTestHarness" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
   <title>Conversion Test Harness</title>
</head>
<body>
    <form id="form1" runat="server">
      <div>
         <asp:DataGrid ID="dgTest" runat="server"></asp:DataGrid>
      </div>
    </form>
</body>
</html>
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls; 
using Coreweb.Example; 

public partial class ConversionTestHarness : System.Web.UI.Page
{
   protected void Page_Load(object sender, EventArgs e)
   {
      this.DemonstrationTest();
   } 
   private void DemonstrationTest()
   {
      CoreWebList coreWebTestList = new CoreWebList(); 
      coreWebTestList.Add(new Employee("Albert", "Einstein", 
         Convert.ToDateTime("3/14/1879")));
      coreWebTestList.Add(new Employee("John", "von Neumann",
Convert.ToDateTime("12/28/1903"))); coreWebTestList.Add(new Employee("Joe", "Finsterwald",
Convert.ToDateTime("1/18/1969"))); coreWebTestList.Add(new Employee("Erwin", "Schr�dinger",
Convert.ToDateTime("8/12/1887"))); DataTable dt = (DataTable)coreWebTestList; DataView dv = new DataView(dt); dv.Sort = "BirthDate"; dgTest.DataSource = dv; dgTest.DataBind(); } }

Conclusion:

Now you have the ability to explicitly convert any decorated container class from a List to a DataTable! Hopefully I�ve also piqued your interest in Aspect Orient Programming!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here