2009-02-19 2 views
194

У меня есть несколько методов, которые возвращают разные общие списки.Конвертировать общий список/Перечислить в DataTable?

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

Если у меня есть это:

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

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

DataTable dt = (DataTable) whatever; 
+2

Конечно, хороший вопрос будет «почему?«Когда список во многих случаях является лучшим инструментом, чем DataTable; -p Каждый из них, я думаю ... –

+1

Я думаю, что это может быть дубликат этого вопроса: http://stackoverflow.com/questions/ 523153/how-do-i-transform-a-listt-in-a-dataset У него даже есть почти идентичный ответ. :-) – mezoid

+1

@MarcGravell: Мое «почему?» - это список манипуляций (перемещение столбцов и строк) . Я пытаюсь сделать стержень из списка и получить доступ к свойствам через рефлексию, это боль. Я делаю это неправильно? –

ответ

254

Вот хороший обновление 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); 
} 

редактора Дис/взыскатель: FastMember проект Марк Gravell. Его золото и полные мухи!


Да, это почти полная противоположность this; рефлексии хватит - или если вам нужно быстрее, 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   
    } 
} 
+0

хороший, но разве это не медленный? просто интересуюсь. – user29964

+3

Ну, «как есть», это будет так же быстро, как отражение. Если вы включите HyperDescriptor, он будет разбивать руки отражения ... Я проведу быстрый тест ... (2 минуты) –

+0

Выражение было упомянуто для 3.5. Если используется, как это повлияет на код, есть ли образец? – MicMit

4

попробовать это

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

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

Его пылающий быстро. Вы можете найти его здесь: ModelShredder on GoogleCode

11

Это простое сочетание решений. Работает с 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; 
} 
171

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

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

ОК. Это просто спасло мне кучу времени! Благодарю. – pStan

+1

+1 для обработки нулей – joshua

+0

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

5

Это также возможно через XmlSerialization. Идея - сериализовать XML, а затем метод readXml 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); 
    } 

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

6

Эта ссылка на MSDN стоит посетить: How to: Implement CopyToDataTable<T> Where the Generic Type T Is Not a 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(); 
3

ответ Марк 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 
9

Небольшое изменение на Mark's answer, чтобы заставить его работать со значениями типа, например 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; 
} 
+0

Как сделать это для списка ? – Muflix

+1

Метод выше будет работать и для int (и других типов значений) ... int - тип значения. см. https://msdn.microsoft.com/en-us/library/s1ax56ch.aspx –

+0

Спасибо, я попробовал это раньше, но у меня были некоторые ошибки, но это было связано с чем-то еще – Muflix

2

Мне также пришлось придумать альтернативное решение, так как ни один из вариантов, перечисленных здесь, не работал в моем случае. Я использовал IEnumerable, который возвращал IEnumerable, и свойства не могли быть перечислены. Это сделал трюк:

// remove "this" if not on C# 3.0/.NET 3.5 
public static DataTable ConvertToDataTable<T>(this IEnumerable<T> data) 
{ 
    List<IDataRecord> list = data.Cast<IDataRecord>().ToList(); 

    PropertyDescriptorCollection props = null; 
    DataTable table = new DataTable(); 
    if (list != null && list.Count > 0) 
    { 
     props = TypeDescriptor.GetProperties(list[0]); 
     for (int i = 0; i < props.Count; i++) 
     { 
      PropertyDescriptor prop = props[i]; 
      table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType); 
     } 
    } 
    if (props != null) 
    { 
     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) ?? DBNull.Value; 
      } 
      table.Rows.Add(values); 
     } 
    } 
    return table; 
} 
2
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Web; 
using System.Web.UI; 
using System.Web.UI.WebControls; 
using System.Data; 
using System.ComponentModel; 

public partial class Default3 : System.Web.UI.Page 
{ 
    protected void Page_Load(object sender, EventArgs e) 
    { 
     DataTable dt = new DataTable(); 
     dt = lstEmployee.ConvertToDataTable(); 
    } 
    public static DataTable ConvertToDataTable<T>(IList<T> list) where T : class 
    { 
     try 
     { 
      DataTable table = CreateDataTable<T>(); 
      Type objType = typeof(T); 
      PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(objType); 
      foreach (T item in list) 
      { 
       DataRow row = table.NewRow(); 
       foreach (PropertyDescriptor property in properties) 
       { 
        if (!CanUseType(property.PropertyType)) continue; 
        row[property.Name] = property.GetValue(item) ?? DBNull.Value; 
       } 

       table.Rows.Add(row); 
      } 
      return table; 
     } 
     catch (DataException ex) 
     { 
      return null; 
     } 
     catch (Exception ex) 
     { 
      return null; 
     } 

    } 
    private static DataTable CreateDataTable<T>() where T : class 
    { 
     Type objType = typeof(T); 
     DataTable table = new DataTable(objType.Name); 
     PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(objType); 
     foreach (PropertyDescriptor property in properties) 
     { 
      Type propertyType = property.PropertyType; 
      if (!CanUseType(propertyType)) continue; 

      //nullables must use underlying types 
      if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) 
       propertyType = Nullable.GetUnderlyingType(propertyType); 
      //enums also need special treatment 
      if (propertyType.IsEnum) 
       propertyType = Enum.GetUnderlyingType(propertyType); 
      table.Columns.Add(property.Name, propertyType); 
     } 
     return table; 
    } 


    private static bool CanUseType(Type propertyType) 
    { 
     //only strings and value types 
     if (propertyType.IsArray) return false; 
     if (!propertyType.IsValueType && propertyType != typeof(string)) return false; 
     return true; 
    } 
} 
2

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

Так вот пример того, что я сделал:

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

. Можете ли вы показать мне пример. как я использую метод расширения для методов addtodataTable() –

+0

Этот код уже имеет в нем пример - взгляните на метод Main(). В последнем бите кода используется расширение. – brenton

+0

Для дальнейшего чтения, пожалуйста, ознакомьтесь с этой статьей из MSDN о методах расширения: https://msdn.microsoft.com/en-us/library/bb383977.aspx – brenton

4
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; 
} 
+3

Хотя это код может ответить на вопрос, предоставив дополнительный контекст относительно того, почему и/или как этот код отвечает на вопрос, улучшает его долгосрочную ценность. – kayess

-1

Другой подход выше:

List<WhateEver> lst = getdata(); 
    string json = Newtonsoft.Json.JsonConvert.SerializeObject(lst); 
    DataTable pDt = JsonConvert.DeserializeObject<DataTable>(json); 
0

Это простой консольного приложения для преобразования списка в DataTable.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Data; 
using System.ComponentModel; 

namespace ConvertListToDataTable 
{ 
    public static class Program 
    { 
     public static void Main(string[] args) 
     { 
      List<MyObject> list = new List<MyObject>(); 
      for (int i = 0; i < 5; i++) 
      { 
       list.Add(new MyObject { Sno = i, Name = i.ToString() + "-KarthiK", Dat = DateTime.Now.AddSeconds(i) }); 
      } 

      DataTable dt = ConvertListToDataTable(list); 
      foreach (DataRow row in dt.Rows) 
      { 
       Console.WriteLine(); 
       for (int x = 0; x < dt.Columns.Count; x++) 
       { 
        Console.Write(row[x].ToString() + " "); 
       } 
      } 
      Console.ReadLine(); 
     } 

     public class MyObject 
     { 
      public int Sno { get; set; } 
      public string Name { get; set; } 
      public DateTime Dat { get; set; } 
     } 

     public static DataTable ConvertListToDataTable<T>(this List<T> iList) 
     { 
      DataTable dataTable = new DataTable(); 
      PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T)); 
      for (int i = 0; i < props.Count; i++) 
      { 
       PropertyDescriptor propertyDescriptor = props[i]; 
       Type type = propertyDescriptor.PropertyType; 

       if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) 
        type = Nullable.GetUnderlyingType(type); 

       dataTable.Columns.Add(propertyDescriptor.Name, type); 
      } 
      object[] values = new object[props.Count]; 
      foreach (T iListItem in iList) 
      { 
       for (int i = 0; i < values.Length; i++) 
       { 
        values[i] = props[i].GetValue(iListItem); 
       } 
       dataTable.Rows.Add(values); 
      } 
      return dataTable; 
     } 
    } 
} 
0

Просьба добавить комментарий (in Russian): Ваше имя:

// remove "this" if not on C# 3.0/.NET 3.5 
    public static DataTable ToDataTable<T>(IList<T> data) 
    { 
     PropertyDescriptorCollection props = 
      TypeDescriptor.GetProperties(typeof(T)); 
     DataTable table = new DataTable(); 
     Type Propiedad = null; 
     for (int i = 0; i < props.Count; i++) 
     { 
      PropertyDescriptor prop = props[i]; 
      Propiedad = prop.PropertyType; 
      if (Propiedad.IsGenericType && Propiedad.GetGenericTypeDefinition() == typeof(Nullable<>)) 
      { 
       Propiedad = Nullable.GetUnderlyingType(Propiedad); 
      } 
      table.Columns.Add(prop.Name, Propiedad); 
     } 
     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; 
    } 
+0

Добро пожаловать в [so]. Это англоязычный веб-сайт, поэтому, пожалуйста, напишите ваши ответы на английском языке. – Fairy

0
Dim counties As New List(Of County) 
Dim dtCounties As DataTable 
dtCounties = _combinedRefRepository.Get_Counties() 
If dtCounties.Rows.Count <> 0 Then 
    For Each row As DataRow In dtCounties.Rows 
     Dim county As New County 
     county.CountyId = row.Item(0).ToString() 
     county.CountyName = row.Item(1).ToString().ToUpper() 
     counties.Add(county) 
    Next 
    dtCounties.Dispose() 
End If 
Смежные вопросы