我有几个方法返回不同的泛型列表。

在。net中存在任何类静态方法或将任何列表转换为数据表?我唯一能想到的是使用反射来做到这一点。

如果我有这个:

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

(这下代码当然不工作,但我想有这样的可能性:

DataTable dt = (DataTable) whatever;

当前回答

如果你想使用反射并设置列的顺序/只包括一些列/排除一些列,试试这个:

        private static DataTable ConvertToDataTable<T>(IList<T> data, string[] fieldsToInclude = null,
string[] fieldsToExclude = null)
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
        DataTable table = new DataTable();
        foreach (PropertyDescriptor prop in properties)
        {
            if ((fieldsToInclude != null && !fieldsToInclude.Contains(prop.Name)) ||
                (fieldsToExclude != null && fieldsToExclude.Contains(prop.Name)))
                continue;
            table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
        }

        foreach (T item in data)
        {
            var atLeastOnePropertyExists = false;
            DataRow row = table.NewRow();
            foreach (PropertyDescriptor prop in properties)
            {

                if ((fieldsToInclude != null && !fieldsToInclude.Contains(prop.Name)) ||
(fieldsToExclude != null && fieldsToExclude.Contains(prop.Name)))
                    continue;

                row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
                atLeastOnePropertyExists = true;
            }

            if(atLeastOnePropertyExists) table.Rows.Add(row);
        }


        if (fieldsToInclude != null)
            SetColumnsOrder(table, fieldsToInclude);

        return table;

    }

    private static void SetColumnsOrder(DataTable table, params String[] columnNames)
    {
        int columnIndex = 0;
        foreach (var columnName in columnNames)
        {
            table.Columns[columnName].SetOrdinal(columnIndex);
            columnIndex++;
        }
    }

其他回答

试试这个

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

下面是NuGet使用FastMember的2013年更新:

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

它使用FastMember的元编程API来获得最大的性能。如果你想将它限制到特定的成员(或强制执行顺序),那么你也可以这样做:

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

FastMember是Marc Gravell的一个项目。它是金色的,完全是苍蝇!


是的,这个和这个完全相反;反射就足够了——或者如果你需要更快,2.0中的HyperDescriptor,或者3.5中的Expression。实际上,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;        
}

现在,只需一行代码,你就可以让它比反射快很多很多倍(通过为对象类型T启用HyperDescriptor)。


编辑重新性能查询;下面是测试结果:

Vanilla 27179
Hyper   6997

我怀疑瓶颈已经从成员访问转移到数据表性能…我怀疑你在这方面还能提高多少……

代码:

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

我意识到这里已经关门有一段时间了;然而,我有一个解决这个特定问题的解决方案,但需要稍微扭转:列和数据表需要预先定义/已经实例化。然后我只需将类型插入到数据表中。

下面是我所做的一个例子:

public static class Test
{
    public static void Main()
    {
        var dataTable = new System.Data.DataTable(Guid.NewGuid().ToString());

        var columnCode = new DataColumn("Code");
        var columnLength = new DataColumn("Length");
        var columnProduct = new DataColumn("Product");

        dataTable.Columns.AddRange(new DataColumn[]
            {
                columnCode,
                columnLength,
                columnProduct
            });

        var item = new List<SomeClass>();

        item.Select(data => new
        {
            data.Id,
            data.Name,
            data.SomeValue
        }).AddToDataTable(dataTable);
    }
}

static class Extensions
{
    public static void AddToDataTable<T>(this IEnumerable<T> enumerable, System.Data.DataTable table)
    {
        if (enumerable.FirstOrDefault() == null)
        {
            table.Rows.Add(new[] {string.Empty});
            return;
        }

        var properties = enumerable.FirstOrDefault().GetType().GetProperties();

        foreach (var item in enumerable)
        {
            var row = table.NewRow();
            foreach (var property in properties)
            {
                row[property.Name] = item.GetType().InvokeMember(property.Name, BindingFlags.GetProperty, null, item, null);
            }
            table.Rows.Add(row);
        }
    }
}
It's also possible through XmlSerialization.
The idea is - serialize to `XML` and then `readXml` method of `DataSet`.

I use this code (from an answer in SO, forgot where)

        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();
        }
    }

so then it's as simple as:

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

Not sure how it stands against all the other answers to this post, but it's also a possibility.

2019年的答案,如果你正在使用。net Core——使用Nuget ToDataTable库。优点:

性能优于FastMember 还可以创建结构化的SqlParameters作为SQL Server表值参数

免责声明-我是ToDataTable的作者

性能——我扩展了一些Benchmark . net测试,并将它们包含在ToDataTable repo中。结果如下:

创建100,000行数据表:

                           MacOS         Windows
Reflection                 818.5 ms      818.3 ms
FastMember from           1105.5 ms      976.4 ms
 Mark's answer
Improved FastMember        524.6 ms      456.4 ms
ToDataTable                449.0 ms      376.5 ms

Marc回答中建议的FastMember方法的性能似乎比Mary使用反射的回答差,但我使用FastMember TypeAccessor滚动了另一个方法,它的性能要好得多。尽管如此,ToDataTable包的性能还是优于其他包。