IEnumerable как проблема производительности DataTable - PullRequest
3 голосов
/ 17 августа 2011

У меня есть следующее расширение, которое генерирует DataTable из 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;
    }

Однако на огромных объемах данных производительность не очень хорошая.Есть ли какие-то очевидные исправления производительности, которые я не могу увидеть?

Ответы [ 4 ]

2 голосов
/ 17 августа 2011

Сначала пара бесполезных проблем:

  1. Тип первого элемента в перечислимом может быть подклассом T, который определяет свойства, которые могут отсутствовать в других элементах. Чтобы избежать проблем, которые могут возникнуть, используйте тип T в качестве источника списка свойств.
  2. Тип может иметь свойства, которые либо не имеют получателя, либо имеют индексированный получатель. Ваш код не должен пытаться прочитать их значения.

Что касается совершенства, я вижу потенциальные улучшения как в аспектах отражения, так и в загрузке таблицы данных:

  1. Кэшируйте получатели свойств и вызывайте их напрямую.
  2. Избегайте доступа к столбцам строк данных по имени для установки значений строк.
  3. Поместить таблицу данных в режим «загрузки данных» при добавлении строк.

С этими модами вы получите что-то вроде следующего:

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;
}
2 голосов
/ 17 августа 2011

Вы всегда можете использовать библиотеку, такую ​​как Fasterflect , чтобы испускать IL, вместо использования истинного отражения для каждого свойства каждого элемента в списке.Не уверен насчет каких-либо ошибок с DataTable.

В качестве альтернативы, если этот код не пытается быть универсальным решением, вы всегда можете иметь любой тип внутри IEnumerable, переводящий себя в DataRowтаким образом избегая отражения всего вместе.

1 голос
/ 17 августа 2011

У вас может не быть выбора по этому поводу, но, возможно, посмотрите на архитектуру кода, чтобы узнать, можете ли вы избежать использования DataTable и вместо этого вернуть IEnumerable<T> самостоятельно.

Основная причина (ы) для этого будет:

  1. Вы переходите от IEnumerable к DataTable, который фактически переходит от операции в потоке к операции с буферизацией .

    • Потоковый: используется yield return, так что результаты извлекаются из перечисления только тогда, когда они необходимы. Он не выполняет итерацию всей коллекции сразу, как foreach

    • Буферизуется: перетаскивает все результаты в память (например, заполненную коллекцию, данные или массив), поэтому все расходы производятся сразу.

  2. Если вы можете использовать возвращаемый тип IEnumerable, то вы можете использовать ключевое слово yield return самостоятельно, что означает, что вы распределяете стоимость всего этого отражения, а не производите его все сразу.

1 голос
/ 17 августа 2011

Вместо этого:

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

используйте:

row[pi.Name] = pi.GetValue(t, null);
...