Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IEnumerable as DataTable performance issue

I have the following extension, which generates a DataTable from an IEnumerable:

    public static DataTable AsDataTable<T>(this IEnumerable<T> enumerable)
    {
        DataTable table = new DataTable();

        T first = enumerable.FirstOrDefault();
        if (first == null)
            return table;

        PropertyInfo[] properties = first.GetType().GetProperties();
        foreach (PropertyInfo pi in properties)
            table.Columns.Add(pi.Name, pi.PropertyType);

        foreach (T t in enumerable)
        {
            DataRow row = table.NewRow();
            foreach (PropertyInfo pi in properties)
                row[pi.Name] = t.GetType().InvokeMember(pi.Name, BindingFlags.GetProperty, null, t, null);
            table.Rows.Add(row);
        }

        return table;
    }

However, on huge amounts of data, the performance isn't very good. Is there any obvious performance fixes that I'm unable to see?

like image 859
Kjetil Avatar asked Dec 30 '25 11:12

Kjetil


2 Answers

First, a couple of non-perf problems:

  1. The type of the first item in the enumerable might be a subclass of T that defines properties that might not be present on other items. To avoid problems that this may cause, use the T type as the source of the properties list.
  2. The type might have properties that either have no getter or that have an indexed getter. Your code should not attempt to read their values.

On the perf side of things, I can see potential improvements on both the reflection and the data table loading sides of things:

  1. Cache the property getters and invoke them directly.
  2. Avoid accessing the data row columns by name to set the row values.
  3. Place the data table in "data loading" mode while adding the rows.

With these mods, you would end up with something like the following:

public static DataTable AsDataTable<T>(this IEnumerable<T> enumerable)
{
    if (enumerable == null)
    {
        throw new ArgumentNullException("enumerable");
    }

    DataTable table = new DataTable();
    if (enumerable.Any())
    {
        IList<PropertyInfo> properties = typeof(T)
                                            .GetProperties()
                                            .Where(p => p.CanRead && (p.GetIndexParameters().Length == 0))
                                            .ToList();

        foreach (PropertyInfo property in properties)
        {
            table.Columns.Add(property.Name, property.PropertyType);
        }

        IList<MethodInfo> getters = properties.Select(p => p.GetGetMethod()).ToList();

        table.BeginLoadData();
        try
        {
            object[] values = new object[properties.Count];
            foreach (T item in enumerable)
            {
                for (int i = 0; i < getters.Count; i++)
                {
                    values[i] = getters[i].Invoke(item, BindingFlags.Default, null, null, CultureInfo.InvariantCulture);
                }

                table.Rows.Add(values);
            }
        }
        finally
        {
            table.EndLoadData();
        }
    }

    return table;
}
like image 56
Nicole Calinoiu Avatar answered Jan 01 '26 23:01

Nicole Calinoiu


Instead of doing:

row[pi.Name] = t.GetType().InvokeMember(pi.Name, BindingFlags.GetProperty, null, t, null);

use:

row[pi.Name] = pi.GetValue(t, null);
like image 37
Islam Yahiatene Avatar answered Jan 02 '26 00:01

Islam Yahiatene



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!