Click here to Skip to main content
11,505,486 members (61,675 online)
Click here to Skip to main content

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

, 22 Aug 2006 100.4K 788 83
Rate this:
Please Sign up or sign in to vote.
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 <BR>      // 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.<BR>            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<BR>                                         = member.Name.ToString().Split(
                                                       Convert.ToChar(" "));
                             PropertyInfo prop = type.GetProperty(<BR>                                                       nameArray[0]);
                             Type valueType = prop.GetValue(items[i], <BR>                                                            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 <BR>      // 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 <BR>                        // your DataTable
                        string[] classNameArray = <BR>                                  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 <BR>                        // 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 && <BR>                   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<EMPLOYEE> coreWebTestList = new CoreWebList<EMPLOYEE>(); 
      coreWebTestList.Add(new Employee("Albert", "Einstein", 
         Convert.ToDateTime("3/14/1879")));
      coreWebTestList.Add(new Employee("John", "von Neumann",<BR>          Convert.ToDateTime("12/28/1903")));
      coreWebTestList.Add(new Employee("Joe", "Finsterwald",<BR>          Convert.ToDateTime("1/18/1969")));
      coreWebTestList.Add(new Employee("Erwin", "Schrödinger",<BR>          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

Share

About the Author

Joseph Finsterwald
Web Developer
United States United States
Joseph Finsterwald lives in Boston, MA. For the past several years he has designed and implemented a number of enterprise applications for leading companies and academic institutions. He is fluent in a number of technologies, but his primary focus has been on .NET and object oriented development. He also has substantial experience in building both Sarbanes Oxley and CFR 21 part 11 compliant solutions. Joe has held positions at Barclays Global Investors, Boston Properties, Harvard Clinical Research Institute, Geonetics and Coreweb. He is currently working at mStyle as a senior engineer.

Comments and Discussions

 
GeneralWith out Web Pin
Member 12407686-Oct-09 20:30
memberMember 12407686-Oct-09 20:30 
GeneralNot the best solution Pin
chaos_2k6-Aug-09 22:09
memberchaos_2k6-Aug-09 22:09 
GeneralRe: Not the best solution Pin
finsj66628-Dec-09 12:38
memberfinsj66628-Dec-09 12:38 
QuestionI'm having an issue with a list of my own type Pin
chris portela4-May-09 16:34
memberchris portela4-May-09 16:34 
AnswerLinq to Sql Update Pin
_Groker10-Jul-08 8:54
member_Groker10-Jul-08 8:54 
GeneralGreat Code ! Please advice on this Error Pin
SanjayDBS12-Feb-08 22:14
memberSanjayDBS12-Feb-08 22:14 
GeneralRe: Great Code ! Please advice on this Error Pin
SanjayDBS12-Feb-08 22:31
memberSanjayDBS12-Feb-08 22:31 
GeneralGreat Pin
Todd Smith7-Dec-07 9:04
memberTodd Smith7-Dec-07 9:04 
GeneralRe: Great [modified] Pin
tommy skaue6-Mar-08 1:40
membertommy skaue6-Mar-08 1:40 
Questionhow to remove by PK & how to define more than one constructor Pin
ab_dc5-Dec-07 3:29
memberab_dc5-Dec-07 3:29 
Generalgreat but small Q Pin
ab_dc28-Nov-07 1:53
memberab_dc28-Nov-07 1:53 
GeneralAre changes also changed in the List Pin
punkrandy15-Apr-07 0:07
memberpunkrandy15-Apr-07 0:07 
GeneralRe: Are changes also changed in the List Pin
Joseph Finsterwald15-Apr-07 8:49
memberJoseph Finsterwald15-Apr-07 8:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150520.1 | Last Updated 22 Aug 2006
Article Copyright 2006 by Joseph Finsterwald
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid