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
In any modern application, you deal with a lot of custom classes, like
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 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.
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:
public class CustomClass1
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
AttributeTargets.Field | AttributeTargets.Method)]
public sealed class DataTableIncludeAttribute : System.Attribute
Reflect and create data columns
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
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:
public void DefineColumns(object obj)
Type type = obj.GetType();
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
Each of them return an array of
MethodInfo that we pass to
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() 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:
- For a field:
- For a method:
This type (e.g.,
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.
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
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:
private string _name = string.Empty;
_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
Once all columns are added, we should add rows with data from our custom objects. This is achieved by using the method:
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
- Enumerate over all columns assigned to the data table
For each column retrieved, the function does the following:
- Check if this column is in the list
DefineColumns() has created
- If so, retrieve the
MemberInfo and use it to retrieve the current value
- For a property or a field, use
GetValue(), for a method invoke it using
- 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:
ArrayList list = new ArrayList();
for (int i = 0; i < 30; i++)
CustomClass1 cc = new CustomClass1();
cc.Age = i;
cc.Name = "John Doe " + i;
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:
private long _myField=13;
public string Prop
return (_myField + 10).ToString();
protected long GetCurrentTickCount()
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:
CustomClassSimple ccs = new CustomClassSimple();
DataTableGenerator generator = new DataTableGenerator();
grid1.DataSource = generator.DataTable;
The data grid will then display:
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.
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:
DataTableGenerator generator =
You can also OR several options together like this:
DataTableGenerator generator =
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.
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.
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.