将通用List/Enumerable转换为DataTable?

Jos*_*ema 245 c# generics datatable list

我有几个返回不同通用列表的方法.

在.net中存在任何类静态方法或将任何列表转换为数据表的任何东西?我唯一可以想象的是使用Reflection来做到这一点.

如果我有这个:

List<Whatever> whatever = new List<Whatever>();
Run Code Online (Sandbox Code Playgroud)

(下一个代码当然不起作用,但我想有可能:

DataTable dt = (DataTable) whatever;
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 310

这是使用NuGet的FastMember进行的 2013年更新:

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

这使用FastMember的元编程API来获得最佳性能.如果您想将其限制为特定成员(或强制执行订单),那么您也可以这样做:

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

编辑的Dis/claimer : FastMember是一个Marc Gravell项目.它的黄金和全飞!


是的,这几乎是完全相反的一个; 反射就足够了 - 或者如果你需要更快,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;        
}
Run Code Online (Sandbox Code Playgroud)

现在使用一行,您可以比反射快许多倍(通过启用HyperDescriptor对象类型T).


编辑重新性能查询; 这是一个测试台,结果如下:

Vanilla 27179
Hyper   6997
Run Code Online (Sandbox Code Playgroud)

我怀疑瓶颈已从成员访问转移到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        
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Ellesedil我努力记住要明确地披露这些事情,但是因为我不是*卖*任何东西(而是在免费提供许多小时的工作)我承认我在这里感觉不到大量的*内疚. . (8认同)
  • 那么"按原样",它将与反射一样快.如果启用HyperDescriptor,它会反射掉反射...我会快速测试......(2分钟) (4认同)
  • @MarcGravell是的我会对Expression解决方案很感兴趣.对于需要快速+学习效果的东西.谢谢马克! (3认同)
  • 可能值得一提的是,出于透明度考虑,您是 FastMember 的作者。您的编辑看起来就像您碰巧遇到的这个很棒的软件包现在可用一样。 (2认同)
  • 您的方法ToDataTable不支持可为空的字段:其他信息:DataSet不支持System.Nullable &lt;&gt;。 (2认同)

Mar*_*lin 222

我不得不修改Mark Gravell的示例代码来处理可空类型和空值.我在下面列出了一个工作版本.谢谢马克.

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 要实现@Jim Beam,请更改方法签名以接受GroupBy的返回:`public static DataTable ToDataTable &lt;TKey,T&gt;(此IEnumerable &lt;IGrouping &lt;TKey,T &gt;&gt;数据)`然后,在foreach循环:`table.Columns.Add(“ Key”,Nullable.GetUnderlyingType(typeof(TKey))?? typeof(TKey));`然后在数据循环周围添加一个循环,在其中迭代组:foreach(IGrouping数据中的&lt;TKey,T&gt;组){foreach(group.Items中的T项目){有关详细信息,请参见此GIST:https://gist.github.com/rickdailey/8679306 (2认同)
  • 这是一篇旧帖子,所以不确定这条评论有多大用处,但在这个 `ToDataTable` 方法中有一个偷偷摸摸的错误。如果 `T` 实现了一个接口 `typeof(T)` 可能会返回接口类型而不是对象的实际类,从而导致一个空的 `DataTable`。用 `data.First().GetType()` 替换它应该可以修复它。 (2认同)

A.B*_*uin 13

这是解决方案的简单组合.它适用于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;
}
Run Code Online (Sandbox Code Playgroud)


Onu*_*mer 12

Mark的答案进行了一些小改动,使其适用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;
}
Run Code Online (Sandbox Code Playgroud)


Jür*_*ock 9

MSDN上的这个链接值得一看:如何:实现CopyToDataTable <T>通用类型T不是DataRow

这会添加一个扩展方法,允许您执行此操作:

// 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();
Run Code Online (Sandbox Code Playgroud)


小智 9

List<YourModel> data = new List<YourModel>();
DataTable dataTable = Newtonsoft.Json.JsonConvert.DeserializeObject<DataTable>(Newtonsoft.Json.JsonConvert.SerializeObject(data));
Run Code Online (Sandbox Code Playgroud)


dik*_*kob 7

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 尽管此代码可以回答问题,但提供有关此代码为何和/或如何回答问题的其他上下文,可以提高其长期价值。 (3认同)

kos*_*ch. 7

上面是另一种方法:

  List<WhateEver> lst = getdata();
  string json = Newtonsoft.Json.JsonConvert.SerializeObject(lst);
  DataTable pDt = JsonConvert.DeserializeObject<DataTable>(json);
Run Code Online (Sandbox Code Playgroud)

  • 这是迄今为止我在网上找到的最干净的解决方案。做得好! (2认同)

Sad*_*egh 6

尝试这个

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;
}
Run Code Online (Sandbox Code Playgroud)


Joh*_*lph 5

我自己写了一个小型图书馆来完成这项任务.它仅在第一次将对象类型转换为数据表时使用反射.它会发出一个方法来完成翻译对象类型的所有工作.

它的速度非常快.你可以在这里找到它: GoogleCode上的ModelShredder


Mit*_*hir 5

它也可以通过XmlSerialization.我们的想法是 - 序列化为XML,然后读取DataSet的readXml方法.

我用这个代码(来自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();
    }
}
Run Code Online (Sandbox Code Playgroud)

所以它就像这样简单:

        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); 
    }
Run Code Online (Sandbox Code Playgroud)

不确定它如何反对这篇文章的所有其他答案,但它也是一种可能性.


Cra*_*gen 5

马克·格雷夫(Marc Gravell)的答案,但在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
Run Code Online (Sandbox Code Playgroud)


Mag*_*ana 5

将通用列表转换为数据表

使用 Newtonsoft.Json;

public DataTable GenericToDataTable(IList<T> list)
{
    var json = JsonConvert.SerializeObject(list);
    DataTable dt = (DataTable)JsonConvert.DeserializeObject(json, (typeof(DataTable)));
    return dt;
}
Run Code Online (Sandbox Code Playgroud)