Click here to Skip to main content
15,895,256 members
Articles / Programming Languages / C#
Article

DataTable Generator Using Attributes

Rate me:
Please Sign up or sign in to vote.
3.67/5 (3 votes)
29 Jan 20077 min read 42.8K   483   40   2
This article demonstrates the first version of a DataTable Generator. This generator uses information found in custom attributes on any class and transforms the class, or several instances of it, to a System.Data.DataTable.

Preface

This article demonstrates a first version of a DataTable Generator. This generator uses information found in custom attributes on any class, and transforms this class, or several instances of it, to a System.Data.DataTable.

Problem

In any modern application, you deal with a lot of custom classes, like Employee, Article, or Manager - each of them having their own properties, member variables, and methods.

Often, you need a quick way to capture the current values of the class at once, view the current data contained in them, or simply transform all values to an XML file.

Luckily, there is class that is able to do all of this already: the DataTable. A DataTable can contain thousands of rows, dozens of columns, can be easily viewed by using a grid, and finally, using XmlSerializer, can be turned easily to an XML file.

The only question is how a custom class can transformed to a DataTable? Well, this is exactly where Xteq.Data.DataTableGenerator jumps in.

Xteq.Data.DataTableGenerator

The first solution that might come to mind is to define a custom interface, and that each class that should be transmogrified to a DataTable must implement it. Within this method, it should define which columns should be added, which data types they have, and finally, fill the data table with rows.

However, for the project I needed this generator, this would have been too bloated, and also I simply didn't wanted to add another piece of code for each class.

What I wanted to have was something like this:

C#
public class CustomClass1
{
    //Include this private member 
    [DataTableInclude]
    private string _name = string.Empty;
}

That way, I only need to apply the custom attribute [DataTableInclude] to the members that should be included in the DataTable, and the DataTableGenerator (DTG in short) should do the rest.

Creating this custom attribute was the easy part. Just create a new class, derive it from System.Attribute, and apply the AttributeUsage on the members this attribute can be applied.

As we want to be able to transform properties, member variables, and functions, we add the following on our custom class DataTableIncludeAttribute:

C#
[AttributeUsage(AttributeTargets.Property | 
      AttributeTargets.Field | AttributeTargets.Method)]
public sealed class DataTableIncludeAttribute : System.Attribute
....

Reflect and create data columns

With this Attribute class in place, we can start to create the DTG. The first thing we need to do is to define the columns on our DataTable. Only with columns in place, we can add rows where the data will be shown.

To create a DataColumn, we basically only need to know two things: the name of the column, and the data type.

To retrieve this information, we will use Reflection. Reflection is the method in the .NET Framework to retrieve information about an object (actually, only the type of the object) and use it. For our purpose, we need to know on which parts of a given class (properties, members, or methods) our custom attribute has been applied.

Since DTG will use the retrieved information to define the columns, the function is called DefineColumns and takes a Type:

C#
public void DefineColumns(Type type)

To make the life for all lazy programmers easier, we also define a second method that can be used on an existing instance:

C#
public void DefineColumns(object obj)
{
 //get the underlying type of the object
 Type type = obj.GetType();
 DefineColumns(type);
}

Within DefineColumns(), we need to retrieve all methods, fields, and properties from the given type, and check if on any of these our attribute has been applied. To do this, we retrieve the fields using type.GetFields(), the properties using type.GetProperties(), and the methods with type.GetMethods().

Each of them return an array of FieldInfo, PropertyInfo, or MethodInfo that we pass to EnumerateMembers(). EnumerateMembers() uses the helper function GetAttribute() to retrieve the DataTableInclude attribute. If the attribute exists, it extracts it and passes the MemberInfo (the field, property, or method we are currently checking) and the DataTableInclude attribute to DefineColumn().

Finally, DefineColumn()

DefineColumn() will first check if the given DataTableInclude attribute contains a name. If not, it will use the name of the MemberInfo (the name of the field, property, or method) as the name for the column.

Next, it needs to know the data type that this member encapsulates. Depending on which member it is working on, this information can be found at:

  • For a property: PropertyType
  • For a field: FieldType
  • For a method: ReturnType

This type (e.g., string, int64, long etc.) will then be assigned to the DataType property of the data column.

It then adds this newly created column as well as the current MemberInfo to the internal Dictionary collection (_list) so we can easily access it later.

Sorting

One thing that was requested 10 minutes after other people started using DTG was "How do I control the order of the columns?". My first reaction was to say that this sorting should be done in the front end. But as DTG should make lives easier, it should also be able to control the order in which the columns appear.

This sorting is done through a temporary list (_sort_list) that is used by DefineColumns() and DefineColumn(). The list stores the created data columns as well as the DataTableInclude attribute. Once DefineColumns() has finished enumerating all members, it sorts _sort_list and then adds the contained columns to the data table.

This sorting takes the SortID property of each DataTableInclude attribute and sorts it ascending. For example, if you want to have a member to appear in the first place, simple apply the DataTableInclude attribute like this:

C#
[DataTableInclude(SortID=1)]
private string _name = string.Empty;

As _sort_list contains both the created data column as well as the DataTableInclude attribute class, you can easily implement your own sorting method by changing DataColumnAndDataTableIncludeAttributeComparer.

Adding data

Once all columns are added, we should add rows with data from our custom objects. This is achieved by using the method:

C#
public void Add(object o)

Rather simple, isn't it? And indeed, the function itself is very simple since it only needs to do the following:

  • Create a new DataRow
  • Enumerate over all columns assigned to the data table

For each column retrieved, the function does the following:

  1. Check if this column is in the list DefineColumns() has created
  2. If so, retrieve the MemberInfo and use it to retrieve the current value
  3. For a property or a field, use GetValue(), for a method invoke it using Invoke()
  4. Assign this returned value to the cell of the current column

And that's it.

For your convenience, there is also a second Add() method that takes an IEnumerable and adds all objects that this enumerator returns. A quick example:

C#
ArrayList list = new ArrayList();

//Add some objects
for (int i = 0; i < 30; i++)
{
    CustomClass1 cc = new CustomClass1();
    cc.Age = i;
    cc.Name = "John Doe " + i;
    list.Add(cc);
}

generator.Add(list); //adds 30 rows

Example

After all this, what is the result actually? Well, here's some code to demonstrate how easy DTG can be used. This is a custom class we want to display in a data grid:

C#
using System;
using Xteq.Data;

namespace TestAppDataTableGen
{
    class CustomClassSimple
    {
        [DataTableInclude]
        private long _myField=13;

        [DataTableInclude(SortID=10)]
        public string Prop
        {
            get
            {
                return (_myField + 10).ToString();
            }
        }

        [DataTableInclude("Ticks")]
        protected long GetCurrentTickCount()
        {
            return DateTime.Now.Ticks;
        }
    }
}

Using the DataTableInclude attribute we have defined to include the private field _myField, to include the property "Prop", which should appear on position 10, and also we want to have the value from GetCurrentTickCount(), but the column should be named "Ticks".

To display this class, we only need to write:

C#
//create our custom classs
CustomClassSimple ccs = new CustomClassSimple();
//create the generator
DataTableGenerator generator = new DataTableGenerator();
//use the current instance to define the columns
generator.DefineColumns(ccs);
//add the current object to the data table
generator.Add(ccs);
//use the grid to display the generated data table
grid1.DataSource = generator.DataTable;

The data grid will then display:

Image 1

Private members note

Although DTG is able to retrieve private members (see _myField) in the example above, this will only work at the level that declares this private member.

For example, if you create CustomClassSimple2 that is derived from the example class above, _myField will not show up since it is private to CustomClassSimple2 and thus not accessible.

Search method

When creating a new instance of DTG, you can also pass in one or more values from the DataTableGeneratorSearch enumeration. Since reflecting the passed object takes some time, you might decide that only fields should be checked and included. This can be done by using this constructor:

C#
DataTableGenerator generator = 
   new DataTableGenerator(DataTableGeneratorSearch.Fields);

You can also OR several options together like this:

C#
DataTableGenerator generator = 
  new DataTableGenerator(DataTableGeneratorSearch.Fields| 
      DataTableGeneratorSearch.Properties);

DataTable tricks

Since DTG provides full access to its underlying DataTable using the DataTable property, you can add new columns, remove existing ones, or do whatever you want with it.

Conclusion

As said at the beginning of this article, this is only the first version of the DataTableGenerator. It currently can't handle indexed properties, methods with parameters, can't resolve any internal collections (for example, class A has a field _collection that contains a collection of class B) or private members of the base classes.

But using this code as the basis, it should be easy to implement these or any other features.

Enjoy!

Disclaimer

THE SOFTWARE AND ALL ACCOMPANYING FILES, DATA AND MATERIALS, ARE DISTRIBUTED AND PROVIDED "AS IS" AND WITH NO WARRANTIES OF ANY KIND, WHETHER EXPRESS OR IMPLIED. THE USER ACKNOWLEDGES THAT GOOD DATA PROCESSING PROCEDURE DICTATES THAT ANY PROGRAM, INCLUDING THE SOFTWARE, MUST BE THOROUGHLY TESTED WITH NON-CRITICAL DATA BEFORE THE USER RELIES ON IT, AND THE USER HEREBY ASSUME THE ENTIRE RISK OF USING THE PROGRAM.

IN ADDITION, IN NO EVENT XTEQ SYSTEMS, OR ITS PRINCIPALS, SHAREHOLDERS, OFFICERS, EMPLOYEES, AFFILIATES, CONTRACTORS, SUBSIDIARIES, OR PARENT ORGANIZATIONS, WILL BE LIABLE FOR ANY INDIRECT, INCIDENTAL, CONSEQUENTIAL OR PUNITIVE DAMAGES WHATSOEVER RELATING TO THE USE OF THE SOFTWARE OR TO THE RELATIONSHIP OF THE USER WITH XTEQ SYSTEMS. THIS INCLUDES, BUT IS NOT LIMITED TO, MERCHANTABILITY AND FITNESS FOR A PARTICULAR FUNCTION.

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


Written By
Web Developer
Germany Germany
I like me.

Comments and Discussions

 
GeneralThanks Pin
Bl8nc25-May-07 0:16
Bl8nc25-May-07 0:16 
GeneralNice article Pin
Juraj Borza4-Feb-07 23:44
Juraj Borza4-Feb-07 23:44 
I wanted to code something like this a while ago, but didn't have much time.
This looks like a good approach to the problem.

Thanks!

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

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