Преобразовать общий список / перечисляемый в DataTable? - PullRequest
228 голосов
/ 19 февраля 2009

У меня есть несколько методов, которые возвращают разные общие списки.

Существует ли в .net какой-либо статический метод класса или любой другой способ преобразования любого списка в таблицу данных? Единственное, что я могу себе представить, это использовать Reflection для этого.

ЕСЛИ у меня есть это:

List<Whatever> whatever = new List<Whatever>();

(Этот следующий код, конечно, не работает, но я хотел бы иметь возможность:

DataTable dt = (DataTable) whatever;

Ответы [ 20 ]

294 голосов
/ 19 февраля 2009

Вот хорошее обновление 2013 года с использованием FastMember от NuGet:

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

При этом используется API мета-программирования FastMember для максимальной производительности. Если вы хотите ограничить его определенными членами (или обеспечить выполнение порядка), то вы можете сделать это тоже:

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

Редактор Dis / заявитель: FastMember - это проект Марка Гравелла. Его золото и полные мухи!


Да, это в значительной степени полная противоположность этому одному; отражения было бы достаточно - или, если вам нужно быстрее, HyperDescriptor в 2,0 или, возможно, Expression в 3,5. На самом деле, HyperDescriptor должно быть более чем адекватным.

Например:

// 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;        
}

Теперь с помощью одной строки вы можете сделать это во много раз быстрее, чем отражение (включив HyperDescriptor для типа объекта T).


редактировать повторно запрос производительности; Вот испытательный стенд с результатами:

Vanilla 27179
Hyper   6997

Я подозреваю, что узкое место перешло от доступа пользователей к производительности DataTable ... Я сомневаюсь, что вы значительно улучшите это ...

код:

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        
    }
}
207 голосов
/ 27 апреля 2011

Мне пришлось изменить пример кода Марка Гравелла, чтобы он мог обрабатывать типы и значения NULL. Я включил рабочую версию ниже. Спасибо Марк.

public static DataTable ToDataTable<T>(this IList<T> data)
{
    PropertyDescriptorCollection properties = 
        TypeDescriptor.GetProperties(typeof(T));
    DataTable table = new DataTable();
    foreach (PropertyDescriptor prop in properties)
        table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
    foreach (T item in data)
    {
        DataRow row = table.NewRow();
        foreach (PropertyDescriptor prop in properties)
             row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
        table.Rows.Add(row);
    }
    return table;
}
12 голосов
/ 07 декабря 2010

Это простая смесь решений. Работает с типами Nullable.

public static DataTable ToDataTable<T>(this IList<T> list)
{
  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, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
  }
  object[] values = new object[props.Count];
  foreach (T item in list)
  {
    for (int i = 0; i < values.Length; i++)
      values[i] = props[i].GetValue(item) ?? DBNull.Value;
    table.Rows.Add(values);
  }
  return table;
}
10 голосов
/ 27 января 2013

Небольшое изменение на Ответ Марка , чтобы заставить его работать с типами значений, такими как List<string> для таблицы данных:

public static DataTable ListToDataTable<T>(IList<T> data)
{
    DataTable table = new DataTable();

    //special handling for value types and string
    if (typeof(T).IsValueType || typeof(T).Equals(typeof(string)))
    {

        DataColumn dc = new DataColumn("Value");
        table.Columns.Add(dc);
        foreach (T item in data)
        {
            DataRow dr = table.NewRow();
            dr[0] = item;
            table.Rows.Add(dr);
        }
    }
    else
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
        foreach (PropertyDescriptor prop in properties)
        {
            table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
        }
        foreach (T item in data)
        {
            DataRow row = table.NewRow();
            foreach (PropertyDescriptor prop in properties)
            {
                try
                {
                    row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
                }
                catch (Exception ex)
                {
                    row[prop.Name] = DBNull.Value;
                }
            }
            table.Rows.Add(row);
        }
    }
    return table;
}
8 голосов
/ 22 февраля 2012

Эту ссылку на MSDN стоит посетить: Как: реализовать CopyToDataTable , где универсальный тип T не является строкой данных

Это добавляет метод расширения, который позволяет вам сделать это:

// Create a sequence. 
Item[] items = new Item[] 
{ new Book{Id = 1, Price = 13.50, Genre = "Comedy", Author = "Gustavo Achong"}, 
  new Book{Id = 2, Price = 8.50, Genre = "Drama", Author = "Jessie Zeng"},
  new Movie{Id = 1, Price = 22.99, Genre = "Comedy", Director = "Marissa Barnes"},
  new Movie{Id = 1, Price = 13.40, Genre = "Action", Director = "Emmanuel Fernandez"}};

// Query for items with price greater than 9.99.
var query = from i in items
             where i.Price > 9.99
             orderby i.Price
             select i;

// Load the query results into new DataTable.
DataTable table = query.CopyToDataTable();
7 голосов
/ 03 декабря 2015
public DataTable ConvertToDataTable<T>(IList<T> data)
{
    PropertyDescriptorCollection properties =
        TypeDescriptor.GetProperties(typeof(T));

    DataTable table = new DataTable();

    foreach (PropertyDescriptor prop in properties)
            table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);

    foreach (T item in data)
    {
        DataRow row = table.NewRow();
        foreach (PropertyDescriptor prop in properties)
        {
           row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
        }
        table.Rows.Add(row);
    }
    return table;
}
5 голосов
/ 14 мая 2009

попробуйте

public static DataTable ListToDataTable<T>(IList<T> lst)
{

    currentDT = CreateTable<T>();

    Type entType = typeof(T);

    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entType);
    foreach (T item in lst)
    {
        DataRow row = currentDT.NewRow();
        foreach (PropertyDescriptor prop in properties)
        {

            if (prop.PropertyType == typeof(Nullable<decimal>) || prop.PropertyType == typeof(Nullable<int>) || prop.PropertyType == typeof(Nullable<Int64>))
            {
                if (prop.GetValue(item) == null)
                    row[prop.Name] = 0;
                else
                    row[prop.Name] = prop.GetValue(item);
            }
            else
                row[prop.Name] = prop.GetValue(item);                    

        }
        currentDT.Rows.Add(row);
    }

    return currentDT;
}

public static DataTable CreateTable<T>()
{
    Type entType = typeof(T);
    DataTable tbl = new DataTable(DTName);
    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entType);
    foreach (PropertyDescriptor prop in properties)
    {
        if (prop.PropertyType == typeof(Nullable<decimal>))
             tbl.Columns.Add(prop.Name, typeof(decimal));
        else if (prop.PropertyType == typeof(Nullable<int>))
            tbl.Columns.Add(prop.Name, typeof(int));
        else if (prop.PropertyType == typeof(Nullable<Int64>))
            tbl.Columns.Add(prop.Name, typeof(Int64));
        else
             tbl.Columns.Add(prop.Name, prop.PropertyType);
    }
    return tbl;
}
5 голосов
/ 23 февраля 2012

Марк Марк Гравелл, но в VB.NET

Public Shared Function ToDataTable(Of T)(data As IList(Of T)) As DataTable
    Dim props As PropertyDescriptorCollection = TypeDescriptor.GetProperties(GetType(T))
    Dim table As New DataTable()
    For i As Integer = 0 To props.Count - 1
            Dim prop As PropertyDescriptor = props(i)
            table.Columns.Add(prop.Name, prop.PropertyType)
    Next
    Dim values As Object() = New Object(props.Count - 1) {}
    For Each item As T In data
            For i As Integer = 0 To values.Length - 1
                    values(i) = props(i).GetValue(item)
            Next
            table.Rows.Add(values)
    Next
    Return table
End Function
5 голосов
/ 08 декабря 2011

Это также возможно через XmlSerialization. Идея состоит в том, чтобы - сериализовать в XML, а затем читать метод XML DataSet.

Я использую этот код (из ответа в SO, забыл где)

    public static string SerializeXml<T>(T value) where T : class
{
    if (value == null)
    {
        return null;
    }

    XmlSerializer serializer = new XmlSerializer(typeof(T));

    XmlWriterSettings settings = new XmlWriterSettings();

    settings.Encoding = new UnicodeEncoding(false, false);
    settings.Indent = false;
    settings.OmitXmlDeclaration = false;
    // no BOM in a .NET string

    using (StringWriter textWriter = new StringWriter())
    {
        using (XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings))
        {
            serializer.Serialize(xmlWriter, value);
        }
        return textWriter.ToString();
    }
}

так что все просто:

        string xmlString = Utility.SerializeXml(trans.InnerList);

    DataSet ds = new DataSet("New_DataSet");
    using (XmlReader reader = XmlReader.Create(new StringReader(xmlString)))
    { 
        ds.Locale = System.Threading.Thread.CurrentThread.CurrentCulture;
        ds.ReadXml(reader); 
    }

Не уверен, как он противопоставляется всем остальным ответам этого поста, но это тоже возможно.

5 голосов
/ 19 июня 2009

Я сам написал небольшую библиотеку для выполнения этой задачи. Он использует отражение только в первый раз, когда тип объекта должен быть переведен в таблицу данных. Он испускает метод, который выполняет всю работу по переводу типа объекта.

Пылающий быстро. Вы можете найти это здесь: ModelShredder в GoogleCode

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...