How to Convert a Datatable into a Generic List

How do you convert a DataTable into a generic list?

If you're using .NET 3.5, you can use DataTableExtensions.AsEnumerable (an extension method) and then if you really need a List<DataRow> instead of just IEnumerable<DataRow> you can call Enumerable.ToList:

IEnumerable<DataRow> sequence = dt.AsEnumerable();

or

using System.Linq;
...
List<DataRow> list = dt.AsEnumerable().ToList();

How to Convert DataTable to Generic List in C#


using System.Reflection;

Then,

public static List<T> BindList<T>(DataTable dt)
{
// Example 1:
// Get private fields + non properties
//var fields = typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

// Example 2: Your case
// Get all public fields
var fields = typeof(T).GetFields();

List<T> lst = new List<T>();

foreach (DataRow dr in dt.Rows)
{
// Create the object of T
var ob = Activator.CreateInstance<T>();

foreach (var fieldInfo in fields)
{
foreach (DataColumn dc in dt.Columns)
{
// Matching the columns with fields
if (fieldInfo.Name == dc.ColumnName)
{
// Get the value from the datatable cell
object value = dr[dc.ColumnName];

// Set the value into the object
fieldInfo.SetValue(ob, value);
break;
}
}
}

lst.Add(ob);
}

return lst;
}

Example of usage:

DataTable dt1 = SqlHelper.GetTable("select * from employee;");
List<Employee> employees = BindList<Employee>(dt1);

DataTable dt2 = SqlHelper.GetTable("select * from membership;");
List<Membership> lstMembership = BindList<Membership>(dt2);

DataTable dt3 = SqlHelper.GetTable("select * from car order by name;");
List<Car> lstCar = BindList<Car>(dt3);

=====================

Extended Version

Above example assume that the data holds inside the DataTable has the same data type as your Class object's fields.

What if the data is not same as your class object's fields?

Such as null?

So, you might want to extend the method to take care just in case both data type are not the same.

public static List<T> BindList<T>(DataTable dt)
{
// Example 1:
// Get private fields + non properties
//var fields = typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

// Example 2: Your case
// Get all public fields
var fields = typeof(T).GetFields();

List<T> lst = new List<T>();

foreach (DataRow dr in dt.Rows)
{
// Create the object of T
var ob = Activator.CreateInstance<T>();

foreach (var fieldInfo in fields)
{
foreach (DataColumn dc in dt.Columns)
{
// Matching the columns with fields
if (fieldInfo.Name == dc.ColumnName)
{
Type type = fieldInfo.FieldType;

// Get the value from the datatable cell
object value = GetValue(dr[dc.ColumnName], type);

// Set the value into the object
fieldInfo.SetValue(ob, value);
break;
}
}
}

lst.Add(ob);
}

return lst;
}

static object GetValue(object ob, Type targetType)
{
if (targetType == null)
{
return null;
}
else if (targetType == typeof(String))
{
return ob + "";
}
else if (targetType == typeof(int))
{
int i = 0;
int.TryParse(ob + "", out i);
return i;
}
else if (targetType == typeof(short))
{
short i = 0;
short.TryParse(ob + "", out i);
return i;
}
else if (targetType == typeof(long))
{
long i = 0;
long.TryParse(ob + "", out i);
return i;
}
else if (targetType == typeof(ushort))
{
ushort i = 0;
ushort.TryParse(ob + "", out i);
return i;
}
else if (targetType == typeof(uint))
{
uint i = 0;
uint.TryParse(ob + "", out i);
return i;
}
else if (targetType == typeof(ulong))
{
ulong i = 0;
ulong.TryParse(ob + "", out i);
return i;
}
else if (targetType == typeof(double))
{
double i = 0;
double.TryParse(ob + "", out i);
return i;
}
else if (targetType == typeof(DateTime))
{
// do the parsing here...
}
else if (targetType == typeof(bool))
{
// do the parsing here...
}
else if (targetType == typeof(decimal))
{
// do the parsing here...
}
else if (targetType == typeof(float))
{
// do the parsing here...
}
else if (targetType == typeof(byte))
{
// do the parsing here...
}
else if (targetType == typeof(sbyte))
{
// do the parsing here...
}
else if........
..................

return ob;
}

Convert DataTable to Generic List

because table is 2D matrix, so we should return 2D List:

public List<List<string >> retListTable()
{

DataTable dt = new DataTable();
adap.Fill(dt);

List<List<string>> lstTable = new List<List<string>>();

foreach (DataRow row in dt.Rows)
{
List<string> lstRow = new List<string>();
foreach (var item in row.ItemArray )
{
lstRow.Add(item.ToString().Replace("\r\n", string.Empty));
}
lstTable.Add(lstRow );
}

return lstTable ;

}

Datatable to Generic List Conversion

You need to check for DBNull.Value instead, something like this:

if (dr[column.ColumnName] != DBNull.Value)
pro.SetValue(obj, dr[column.ColumnName], null);

Or:

pro.SetValue(obj, dr[column.ColumnName] == DBNull.Value ? string.Empty : 
dr[column.ColumnName].ToString(), null);

Convert DataTable to generic List?

That won't be an actual leak, but it could be stressing things unnecessarily...

How many rows are you working over?
Note that reflection is a pain, and that every call to things like GetCustomAttributes can return a new array (so you want to do that once, not once per-property-per-row).

Personally, I'd pre-construct the work I intend to do... something like below.

Note that if I was doing this lots, I'd either switch to HyperDescriptor, or if .NET 3.5 was an option, maybe a compiled Expression. Since DataTable isn't strongly typed, HyperDescriptor would be a logical next step (for performance) after the below...

sealed class Tuple<T1, T2>
{
public Tuple() {}
public Tuple(T1 value1, T2 value2) {Value1 = value1; Value2 = value2;}
public T1 Value1 {get;set;}
public T2 Value2 {get;set;}
}
public static List<T> Convert<T>(DataTable table)
where T : class, new()
{
List<Tuple<DataColumn, PropertyInfo>> map =
new List<Tuple<DataColumn,PropertyInfo>>();

foreach(PropertyInfo pi in typeof(T).GetProperties())
{
ColumnAttribute col = (ColumnAttribute)
Attribute.GetCustomAttribute(pi, typeof(ColumnAttribute));
if(col == null) continue;
if(table.Columns.Contains(col.FieldName))
{
map.Add(new Tuple<DataColumn,PropertyInfo>(
table.Columns[col.FieldName], pi));
}
}

List<T> list = new List<T>(table.Rows.Count);
foreach(DataRow row in table.Rows)
{
if(row == null)
{
list.Add(null);
continue;
}
T item = new T();
foreach(Tuple<DataColumn,PropertyInfo> pair in map) {
object value = row[pair.Value1];
if(value is DBNull) value = null;
pair.Value2.SetValue(item, value, null);
}
list.Add(item);
}
return list;
}

Fastest way to convert datatable to generic list

Do you know the structure of the DataTable and the typed object ahead of time? You could use a delegate to do the mapping. If you don't (i.e. all you know is a Type and properties) there are ways of accelerating dynamic member access (such as HyperDescriptor).

Either way, consider an iterator block; that way you don't have to buffer the objects an entire second time; of course, if you are only dealing with smallish rowcounts this isn't an issue.

Can you clarify any of those points? I can add a lot more detail...

At the simplest, what is wrong with:

DataTable table = new DataTable {
Columns = {
{"Foo", typeof(int)},
{"Bar", typeof(string)}
}
};
for (int i = 0; i < 5000; i++) {
table.Rows.Add(i, "Row " + i);
}

List<MyType> data = new List<MyType>(table.Rows.Count);
foreach (DataRow row in table.Rows) {
data.Add(new MyType((int)row[0], (string)row[1]));
}

(the problems in the above might steer the right approach...)

Convert DataTable to Generic List in C#

You could actually shorten it down considerably. You can think of the Select() extension method as a type converter. The conversion could then be written as this:

List<Cards> target = dt.AsEnumerable()
.Select(row => new Cards
{
// assuming column 0's type is Nullable<long>
CardID = row.Field<long?>(0).GetValueOrDefault(),
CardName = String.IsNullOrEmpty(row.Field<string>(1))
? "not found"
: row.Field<string>(1),
}).ToList();

Convert generic List/Enumerable to DataTable?

Here's a nice 2013 update using FastMember from NuGet:

IEnumerable<SomeType> data = ...
DataTable table = new DataTable();
using(var reader = ObjectReader.Create(data)) {
table.Load(reader);
}

This uses FastMember's meta-programming API for maximum performance. If you want to restrict it to particular members (or enforce the order), then you can do that too:

IEnumerable<SomeType> data = ...
DataTable table = new DataTable();
using(var reader = ObjectReader.Create(data, "Id", "Name", "Description")) {
table.Load(reader);
}

Editor's Dis/claimer: FastMember is a Marc Gravell project. It's gold and full-on flies!


Yes, this is pretty much the exact opposite of this one; reflection would suffice - or if you need quicker, HyperDescriptor in 2.0, or maybe Expression in 3.5. Actually, HyperDescriptor should be more than adequate.

For example:

// remove "this" if not on C# 3.0 / .NET 3.5
public static DataTable ToDataTable<T>(this IList<T> data)
{
PropertyDescriptorCollection props =
TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
for(int i = 0 ; i < props.Count ; i++)
{
PropertyDescriptor prop = props[i];
table.Columns.Add(prop.Name, prop.PropertyType);
}
object[] values = new object[props.Count];
foreach (T item in data)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = props[i].GetValue(item);
}
table.Rows.Add(values);
}
return table;
}

Now with one line you can make this many many times faster than reflection (by enabling HyperDescriptor for the object-type T).


Edit re performance query; here's a test rig with results:

Vanilla 27179
Hyper 6997

I suspect that the bottleneck has shifted from member-access to DataTable performance... I doubt you'll improve much on that...

Code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
public class MyData
{
public int A { get; set; }
public string B { get; set; }
public DateTime C { get; set; }
public decimal D { get; set; }
public string E { get; set; }
public int F { get; set; }
}

static class Program
{
static void RunTest(List<MyData> data, string caption)
{
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < 500; i++)
{
data.ToDataTable();
}
watch.Stop();
Console.WriteLine(caption + "\t" + watch.ElapsedMilliseconds);
}
static void Main()
{
List<MyData> foos = new List<MyData>();
for (int i = 0 ; i < 5000 ; i++ ){
foos.Add(new MyData
{ // just gibberish...
A = i,
B = i.ToString(),
C = DateTime.Now.AddSeconds(i),
D = i,
E = "hello",
F = i * 2
});
}
RunTest(foos, "Vanilla");
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(
typeof(MyData));
RunTest(foos, "Hyper");
Console.ReadLine(); // return to exit
}
}


Related Topics



Leave a reply



Submit