Convert a Generic List to a DataTable Using Reflection

by Al Beecy May 18, 2009
Today I needed a method to convert a generic list to a DataTable. The list contained simple records such as the class below:

public class MyRecord
{
    public int IntField;
    public int IntProperty {get; set;}
    public string StringProperty {get; set;}
    public bool BoolProperty {get; set;}
    public DateTime DateProperty {get; set;}
}

My first inclination was to just slap together something quick and dirty like:

#region MyRecordListToDataTable
/// <summary>
/// Converts a List<MyRecord> into a DataTable.
/// </summary>
/// <param name="list"></param>
/// <returns>DataTable</returns>
public static DataTable MyRecordListToDataTable(List<MyRecord> list)
{
    DataTable dt = new DataTable("MyRecordList");
    dt.Columns.Add("IntField", list[0].IntField.GetType());
    dt.Columns.Add("IntProperty", list[0].IntProperty.GetType());
    dt.Columns.Add("StringProperty", list[0].StringProperty.GetType());
    dt.Columns.Add("BoolProperty", list[0].BoolProperty.GetType());
    dt.Columns.Add("DateProperty", list[0].DateProperty.GetType());
 
    foreach (MyRecord item in list)
    {
        dt.Rows.Add(
            item.IntField,
            item.IntProperty,
            item.StringProperty,
            item.BoolProperty,
            item.DateProperty);
    }
 
    return dt;
}
#endregion

Admittedly, not the most elegant piece of code ever written, but it got the job done. Then I got to thinking... I use lots of generic lists -- wouldn't it be nice if I had a method that could convert any generic list into a DataTable without me having to hand-code the fields? So I started playing around with reflection to see if I could get what I needed from it.

The first tricky part was figuring out what the object type stored in the list was. I looked around Google and basically only found bad, fragile looking solutions involving parsing various string properties on the Type object. Eventually, I discovered the GetGenericArguments() method on MSDN of all places (MSDN is usually the last place I look for useful information). GetGenericArguments() returns a list of the types used declare the generic type:

Type elementType = listType.GetGenericArguments()[0];

So I was off and running. Getting a list of the members of the list's underlying type was pretty straight-forward, but also kind of useless:

MemberInfo[] miArray = elementType.GetMembers(
    BindingFlags.Public | BindingFlags.Instance);

As I stepped through my code, I could see the data types of my object's various fields using the debugger, but none of the MemberInfo object's public properties exposed the member's type. Finally, I figured out that I had to cast the MemberInfo into either a FieldInfo or a PropertyInfo to get at the good stuff. So now I could construct the table's schema:

foreach (MemberInfo mi in miArray)
{
    if (mi.MemberType == MemberTypes.Property)
    {
        PropertyInfo pi = mi as PropertyInfo;
        dt.Columns.Add(pi.Name, pi.PropertyType);
    }
    else if (mi.MemberType == MemberTypes.Field)
    {
        FieldInfo fi = mi as FieldInfo;
        dt.Columns.Add(fi.Name, fi.FieldType);
    }
}

Now came the really fun part -- how do I iterate through a list (that doesn't know it's a list) containing unknown objects?  First I tried to cast it to a List<object> on the theory that once I could iterate the list I'd be in a much better position to get at the objects it contained. But when I tried to cast it, I got a nasty error message that said: "Unable to cast object of type 'System.Collections.Generic.List`1[MyRecord]' to type 'System.Collections.Generic.List`1[System.Object]'". Well, that kind of sucks.

Next, I decided that there must be some way to use reflection to do the cast since, in theory, it knows everything it needs to about the objects involved but, after about an hour of Googling around, I concluded that there was no way to do it. If someone else knows a way, please drop me a line.

Just when I was about to give up, I stumbled across a post on how to determine if you are dealing with an indexed property when using reflection. It had an example that used the IList interface. Now, the IList interface is an old interface from the System.Collections namespace. I was using System.Collections.Generic, so I didn't really have high hopes, but decided to give it a try anyway. I added a using statement for System.Collections and tried to cast my list to an IList. It worked! I was back in the game:

IList il = list as IList; 

The final piece needed was a way to get at the values of each of my object's fields. I figured it would be possible via the MemberInfo/PropertyInfo/FieldInfo classes. A few minutes of scrounging around the web confirmed this. The Type class had a GetMember() method that took the name of the member to return as a string. And the PropertyInfo and FieldInfo classes had a GetValue() method that, if handed the instance of the object, would extract the value of the property or field they represented.

Since my columns were constructed using the names of the various fields and properties, I had everything I needed populate the table's rows. So, pulling it all together:

#region GenericListToDataTable
/// <summary>
/// Converts a generic List<> into a DataTable.
/// </summary>
/// <param name="list"></param>
/// <returns>DataTable</returns>
public static DataTable GenericListToDataTable(object list)
{
    DataTable dt = null;
    Type listType = list.GetType();
    if (listType.IsGenericType)
    {
        //determine the underlying type the List<> contains
        Type elementType = listType.GetGenericArguments()[0];
 
        //create empty table -- give it a name in case
        //it needs to be serialized
        dt = new DataTable(elementType.Name + "List");
 
        //define the table -- add a column for each public
        //property or field
        MemberInfo[] miArray = elementType.GetMembers(
            BindingFlags.Public | BindingFlags.Instance);
        foreach (MemberInfo mi in miArray)
        {
            if (mi.MemberType == MemberTypes.Property)
            {
                PropertyInfo pi = mi as PropertyInfo;
                dt.Columns.Add(pi.Name, pi.PropertyType);
            }
            else if (mi.MemberType == MemberTypes.Field)
            {
                FieldInfo fi = mi as FieldInfo;
                dt.Columns.Add(fi.Name, fi.FieldType);
            }
        }
 
        //populate the table
        IList il = list as IList;
        foreach (object record in il)
        {
            int i = 0;
            object[] fieldValues = new object[dt.Columns.Count];
            foreach (DataColumn c in dt.Columns)
            {
                MemberInfo mi = elementType.GetMember(c.ColumnName)[0];
                if (mi.MemberType == MemberTypes.Property)
                {
                    PropertyInfo pi = mi as PropertyInfo;
                    fieldValues[i] = pi.GetValue(record, null);
                }
                else if (mi.MemberType == MemberTypes.Field)
                {
                    FieldInfo fi = mi as FieldInfo;
                    fieldValues[i] = fi.GetValue(record);
                }
                i++;
            }
            dt.Rows.Add(fieldValues);
        }
    }
    return dt;
}
#endregion

The astute developers out there will recognize that there are two important things missing from the code above: error handling and code to ensure that only simple data types that SQL understands are included in the table. I left this stuff out of the example above to cut down on the clutter. Obviously, you should add it back in before using this code snippet.

Tags: , ,

C# | Generics | Reflection

Comments

May 18, 2009 #

DotNetKicks.com

Trackback from DotNetKicks.com

Convert a Generic List to a DataTable Using Reflection

DotNetKicks.com

May 18, 2009 #

Web Development Community

Trackback from Web Development Community

C#: Convert a Generic List to a DataTable Using Reflection

Web Development Community

June 14, 2009 #

DotNetShoutout

Convert a Generic List to a DataTable Using Reflection

Thank you for submitting this cool story - Trackback from DotNetShoutout

DotNetShoutout

June 27, 2009 #

Rennie Petersen

Thanks for the tip about casting an object to IList - that was exactly what I was desperately searching for.

Rennie Petersen Denmark

July 2, 2009 #

AKIRA

Thx for your post! I used it to read properties from a generic list. There is even a more generic way.

public static DataTable GenericListToDataTable<T>(List<T> list)

So you don't have to use GetGenericArguments() to find out the type and you can use
foreach (T record in list) for looping through the records and you don't need IList.

AKIRA Switzerland

July 12, 2009 #

Al Beecy

Akira, thanks for the tip. I'll give it a try and post an updated version if works.

Al Beecy United States

September 30, 2009 #

Anonymous

thanks a lot.. i initially skipped over this thinking it may not be what i need.
but later on i came back.. it is really useful. thanks a lot!!
u surely got my 5/5 rating. Smile

Anonymous United States

October 14, 2009 #

Brian Levine

Thanks for this code.  I used it in a Generic class to handle the underlying data for on ObjectDataSource associated with a GridView.  My source data was in the form of a List of objects.  This allows me to use default paging and sorting in my GridView.  Here's an example of the code:

// the data class
namespace ODSClass
{
    public class ODSDataClass
    {
        public DataTable table;

        public ODSDataClass(object list)
        {
            table = GenericListToDataTable(list);
        }

        public DataView Select(string sortExpression)
        {
            DataView dv = new DataView(table);
            dv.Sort = sortExpression;
            return dv;
        }

        public static DataTable GenericListToDataTable(object list)
        {
        ...
        }
    }
}

// the ObjectDataSource
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" TypeName="ODSClass.ODSDataClass"
SelectMethod="Select" SortParameterName="sortExpression" OnObjectCreating="ObjectDataSource1_ObjectCreating">
</asp:ObjectDataSource>

// and the create event handler
protected void ObjectDataSource1_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
{
    ODSClass.ODSDataClass myOrders = new ODSClass.ODSDataClass(GetOrders());
    e.ObjectInstance = myOrders;
}

Works perfectly.  Thanks again.


Brian Levine United States

November 5, 2009 #

Al Beecy

Thanks, Brian! Very useful class. I usually avoid declarative data sources because I hate mixing presentation and business logic (don't even like declarative grid columns Smile, but sometimes it's the easiest way for a quick and dirty page...

Al Beecy United States

December 15, 2009 #

Ugur ERDEM

Thanks  for this sample. it is very usefull for me Smile

Ugur ERDEM Turkey

December 17, 2009 #

Brady Holt

Thanks!  Just what I was looking for...

Brady Holt United States

February 25, 2010 #

Musa Khan

I have taken this a step further and added the code to convert a generic list to a DataTable as an extension method to the generic List class. See article on my blog at www.khanConsultants.co.uk for details

Musa Khan United Kingdom

Powered by BlogEngine.NET1.5.0.7 | Theme by Mads Kristensen