Convertir la lista genérica / enumerable a DataTable?

Tengo algunos métodos que devuelve diferentes listas genéricas.

¿Existe en .net algún método estático de clase o lo que sea para convertir cualquier lista en una tabla de datos? Lo único que puedo imaginar es usar Reflection para hacer esto.

SI tengo esto:

List whatever = new List(); 

(Este próximo código no funciona, por supuesto, pero me gustaría tener la posibilidad de:

 DataTable dt = (DataTable) whatever; 

Aquí hay una buena actualización de 2013 usando FastMember de NuGet:

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

Utiliza la API de metaprogtwigción de FastMember para obtener el máximo rendimiento. Si desea restringirlo a miembros en particular (o hacer cumplir el pedido), puede hacerlo también:

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

Des / claimer del editor: FastMember es un proyecto de Marc Gravell. ¡Es oro y vuela completo!


Sí, esto es más o menos lo contrario de este ; la reflexión sería suficiente, o si necesita más rápido, HyperDescriptor en 2.0 o quizás Expression en 3.5. En realidad, HyperDescriptor debería ser más que adecuado.

Por ejemplo:

 // remove "this" if not on C# 3.0 / .NET 3.5 public static DataTable ToDataTable(this IList 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; } 

Ahora con una línea puede hacer esto muchas veces más rápido que la reflexión (habilitando HyperDescriptor para el tipo de objeto T ).


editar la consulta de rendimiento re; aquí hay una plataforma de prueba con resultados:

 Vanilla 27179 Hyper 6997 

Sospecho que el cuello de botella ha cambiado desde el acceso de los miembros al rendimiento de DataTable ... Dudo que mejore mucho en eso ...

código:

 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 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 foos = new List(); 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 } } 

Tuve que modificar el código de muestra de Mark Gravell para manejar tipos anulables y valores nulos. He incluido una versión de trabajo a continuación. Gracias Mark.

 public static DataTable ToDataTable(this IList 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; } 

Esta es una combinación simple de las soluciones. Funciona con tipos Nullable.

 public static DataTable ToDataTable(this IList 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; } 

Un pequeño cambio en la respuesta de Mark para que funcione con tipos de valores como List en la tabla de datos:

 public static DataTable ListToDataTable(IList 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; } 

Vale la pena visitar este enlace en MSDN: Cómo implementar: CopyToDataTable Donde el tipo genérico T no es un DataRow

Esto agrega un método de extensión que le permite hacer esto:

 // 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 = "Dtwig", 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(); 

También es posible a través de XmlSerialization. La idea es serializar a XML y luego leer el método XML de DataSet.

Uso este código (de una respuesta en SO, olvidé dónde)

  public static string SerializeXml(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(); } } 

Entonces, es tan simple como:

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

No estoy seguro de cómo se compara con todas las otras respuestas de esta publicación, pero también es una posibilidad.

He escrito una pequeña biblioteca para realizar esta tarea. Utiliza la reflexión solo la primera vez que se traduce un tipo de objeto a una tabla de datos. Emite un método que hará todo el trabajo traduciendo un tipo de objeto.

Es increíblemente rápido. Puede encontrarlo aquí: ModelShredder en GoogleCode

La respuesta de Marc Gravell pero en 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 
 public DataTable ConvertToDataTable(IList 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; } 

prueba esto

 public static DataTable ListToDataTable(IList lst) { currentDT = CreateTable(); 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) || prop.PropertyType == typeof(Nullable) || prop.PropertyType == typeof(Nullable)) { 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() { Type entType = typeof(T); DataTable tbl = new DataTable(DTName); PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entType); foreach (PropertyDescriptor prop in properties) { if (prop.PropertyType == typeof(Nullable)) tbl.Columns.Add(prop.Name, typeof(decimal)); else if (prop.PropertyType == typeof(Nullable)) tbl.Columns.Add(prop.Name, typeof(int)); else if (prop.PropertyType == typeof(Nullable)) tbl.Columns.Add(prop.Name, typeof(Int64)); else tbl.Columns.Add(prop.Name, prop.PropertyType); } return tbl; } 

También tuve que buscar una solución alternativa, ya que ninguna de las opciones enumeradas aquí funcionaba en mi caso. Estaba usando un IEnumerable que me devolvió un IEnumerable y las propiedades no pudieron ser enumeradas. Esto hizo el truco:

 // remove "this" if not on C# 3.0 / .NET 3.5 public static DataTable ConvertToDataTable(this IEnumerable data) { List list = data.Cast().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; } 
  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(IList list) where T : class { try { DataTable table = CreateDataTable(); 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() 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; } } 

Me doy cuenta de que esto ha estado cerrado por un tiempo; sin embargo, tenía una solución para este problema específico pero necesitaba un ligero giro: las columnas y la tabla de datos necesitaban estar predefinidos / ya instanciados. Entonces necesité simplemente insertar los tipos en la tabla de datos.

Así que aquí hay un ejemplo de lo que hice:

 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(); item.Select(data => new { data.Id, data.Name, data.SomeValue }).AddToDataTable(dataTable); } } static class Extensions { public static void AddToDataTable(this IEnumerable 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); } } } 

Si está utilizando VB.NET, esta clase hace el trabajo.

 Imports System.Reflection '''  ''' Convert any List(Of T) to a DataTable with correct column types and converts Nullable Type values to DBNull '''  Public Class ConvertListToDataset Public Function ListToDataset(Of T)(ByVal list As IList(Of T)) As DataTable Dim dt As New DataTable() '/* Create the DataTable columns */ For Each pi As PropertyInfo In GetType(T).GetProperties() If pi.PropertyType.IsValueType Then Debug.Print(pi.Name) End If If IsNothing(Nullable.GetUnderlyingType(pi.PropertyType)) Then dt.Columns.Add(pi.Name, pi.PropertyType) Else dt.Columns.Add(pi.Name, Nullable.GetUnderlyingType(pi.PropertyType)) End If Next '/* Populate the DataTable with the values in the Items in List */ For Each item As T In list Dim dr As DataRow = dt.NewRow() For Each pi As PropertyInfo In GetType(T).GetProperties() dr(pi.Name) = IIf(IsNothing(pi.GetValue(item)), DBNull.Value, pi.GetValue(item)) Next dt.Rows.Add(dr) Next Return dt End Function End Class 

Otro enfoque es el anterior:

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

Esta es la aplicación de consola simple para convertir la lista en tabla de datos.

 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 list = new List(); 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(this List 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; } } } 

Se probó el método para que acepte campos con null.

 // remove "this" if not on C# 3.0 / .NET 3.5 public static DataTable ToDataTable(IList 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; } 
  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