¿Convertir lista genérica / enumerable en tabla de datos?

261

Tengo pocos métodos que devuelven 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> whatever = new List<Whatever>();

(El siguiente código no funciona, por supuesto, pero me gustaría tener la posibilidad de:

DataTable dt = (DataTable) whatever;
Josema
fuente
2
Por supuesto, una buena pregunta sería "¿por qué?" - cuando la Lista <T> es en muchos casos una herramienta mejor que DataTable ;-p Cada uno por su cuenta, supongo ...
Marc Gravell
1
Creo que esta puede ser un duplicado de esta pregunta: stackoverflow.com/questions/523153/... Incluso tiene una respuesta casi idéntica. :-)
mezoid
2
@MarcGravell: Mi "¿por qué?" es la manipulación de la Lista <T> (Recorriendo columnas y filas). Estoy tratando de hacer un pivote de una Lista <T> y acceder a las propiedades a través de la reflexión es un dolor. Lo estoy haciendo mal?
Eduardo Molteni
1
@Eduardo, hay muchas herramientas para eliminar el dolor de la reflexión: FastMember salta a la mente. También podría ser que un DataTable sea útil para escenarios específicos; todo depende del contexto. Quizás el mayor problema es que las personas usan DataTable para todo el almacenamiento de datos solo porque existe , sin tomarse el tiempo para considerar las opciones y su escenario.
Marc Gravell
@EduardoMolteni si está interesado, actualicé FastMember para tener soporte directo para esto - vea la respuesta actualizada
Marc Gravell

Respuestas:

325

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

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

Esto utiliza la API de metaprogramación de FastMember para obtener el máximo rendimiento. Si desea restringirlo a miembros particulares (o hacer cumplir el orden), también puede hacerlo:

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

Editor de Dis / reclamante: FastMember es un proyecto de Marc Gravell. ¡Es oro y moscas!


Sí, esto es casi exactamente lo contrario de este ; la reflexión sería suficiente, o si necesita más rápido, HyperDescriptoren 2.0, o tal vez Expressionen 3.5. En realidad, HyperDescriptordebería ser más que adecuado.

Por ejemplo:

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

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


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

Vanilla 27179
Hyper   6997

Sospecho que el cuello de botella ha cambiado del acceso de los miembros al DataTablerendimiento ... 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<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        
    }
}
Marc Gravell
fuente
44
Bueno "como está", será casi tan rápido como la reflexión. Si habilita HyperDescriptor, golpeará la reflexión con las manos hacia abajo ... Haré una prueba rápida ... (2 minutos)
Marc Gravell
Se mencionó la expresión para 3.5. Si se usa, ¿cómo afectaría el código? ¿Hay alguna muestra?
MicMit
3
@MarcGravell Sí, estaría muy interesado en la solución Expression. Por necesitar algo rápido + efecto de aprendizaje. Gracias Marc!
Elisabeth
11
@Ellesedil Me esfuerzo por recordar revelar explícitamente tales cosas, pero como no estoy vendiendo nada (sino que estoy haciendo muchas horas de trabajo disponibles libremente), confieso que no siento una gran culpa aquí ...
Marc Gravell
2
su método ToDataTable no admite campos anulables: Información adicional: DataSet no admite System.Nullable <>.
Dainius Kreivys
235

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

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;
}
Mary Hamlin
fuente
Esta es una excelente respuesta. Me encantaría ver este ejemplo ampliado para manejar un grupo por lista que contendría una propiedad de elemento y tendría columnas creadas de la misma manera arriba.
Codificador desconocido
2
Para lograr ese @Jim Beam, cambie la firma del método para aceptar el retorno de GroupBy: public static DataTable ToDataTable<TKey, T>(this IEnumerable<IGrouping<TKey, T>> data) Luego, agregue una columna adicional antes del bucle foreach: table.Columns.Add("Key", Nullable.GetUnderlyingType(typeof(TKey)) ?? typeof(TKey)); Y luego agregue un bucle alrededor del bucle de datos donde itera los grupos: foreach (IGrouping <TKey, T> grupo en datos) {foreach (elemento T en grupo. Artículos) {Vea este GIST para más detalles: gist.github.com/rickdailey/8679306
Rick Dailey
oye, ¿hay alguna manera de manejar un objeto con objetos internos? Sólo quiero las propiedades internas que aparecen como columnas después de las columnas de la matriz de objetos
HeyNow
@heyAhora, estoy seguro de que sí. Pero realmente no necesitaba esa funcionalidad con lo que estaba haciendo, por lo que lo dejé para que alguien más la extendiera. :)
Mary Hamlin
1
Esta es una publicación antigua, así que no estoy seguro de cuán útil es este comentario, pero hay un error furtivo en este ToDataTablemétodo. Si Timplementa una interfaz, typeof(T)puede devolver el tipo de interfaz en lugar de la clase real del objeto, lo que da como resultado un vacío DataTable. Reemplazarlo data.First().GetType()debería solucionarlo.
Lucas
14

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

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;
}
Onur Omer
fuente
¿Cómo hacerlo para List <int>?
Muflix
1
El método anterior también funcionará para int (y otros tipos de valor) ... int es un tipo de valor. ver: msdn.microsoft.com/en-us/library/s1ax56ch.aspx
Onur Omer
Me gusta esto porque no depende del uso de un método de extensión. Funciona bien para bases de código más antiguas que pueden no tener acceso a los métodos de extensión.
webworm
13

Esta es una mezcla simple de las soluciones. Funciona con tipos anulables.

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;
}
A.Baudouin
fuente
Esta solución es propensa a errores porque depende del orden de declaración de propiedades en la clase T.
Vahid Ghadiri
10

Vale la pena visitar este enlace en MSDN: Cómo: Implementar CopyToDataTable <T> Donde el tipo genérico T no es una fila de datos

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 = "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();
Jürgen Steinblock
fuente
@PaulWilliams Gracias, utilizo este código durante años sin ningún problema hasta ahora. Pero como no copié el código de ejemplo de Microsoft y solo lo vinculé al sitio web, las otras soluciones son al menos más compatibles con las mejores prácticas de respuesta stackoverflow.com/help/how-to-answer
Jürgen Steinblock
8

Otro enfoque es el anterior:

  List<WhateEver> lst = getdata();
  string json = Newtonsoft.Json.JsonConvert.SerializeObject(lst);
  DataTable pDt = JsonConvert.DeserializeObject<DataTable>(json);
kostas ch.
fuente
Muy, muy bien ... pero arrojó una excepción del tipo 'System.OutOfMemoryException'. Lo usé con 500 000 artículos ... Pero gracias por esto.
st_stefanov
Esta es, con mucho, la solución más limpia que he encontrado en la red. ¡Buen trabajo!
Sarah
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;
}
Boitumelo Dikoko
fuente
3
Si bien este código puede responder la pregunta, proporcionar un contexto adicional con respecto a por qué y / o cómo responde la pregunta mejora su valor a largo plazo.
kayess
Esta solución es propensa a errores porque depende del orden de declaración de propiedades en la clase T.
Vahid Ghadiri
6

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
Craig Gjerdingen
fuente
6

prueba esto

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;
}
Sadegh
fuente
6
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.
Mithir
fuente
5

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

Es muy rápido. Puedes encontrarlo aquí: ModelShredder en GoogleCode

Johannes Rudolph
fuente
2

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

// 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;
}
Michael Brown
fuente
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;
    }
}

fuente
2

Me doy cuenta de que esto ha estado cerrado por un tiempo; sin embargo, tenía una solución a este problema específico pero necesitaba un pequeño giro: las columnas y la tabla de datos debían estar predefinidas / ya instanciadas. Luego necesitaba simplemente insertar los tipos en la tabla de datos.

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<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);
        }
    }
}
brenton
fuente
me puedes mostrar con ejemplo. cómo uso el método de extensión para los métodos addtodataTable ()
Abhishek B.
Este código ya tiene un ejemplo: eche un vistazo al método Main (). El último bit de código tiene la extensión utilizada.
brenton
Para leer más, lea este artículo de MSDN sobre métodos de extensión: msdn.microsoft.com/en-us/library/bb383977.aspx
brenton
2

Una respuesta de 2019 si está utilizando .NET Core: use la biblioteca Nuget ToDataTable . Ventajas:

  • Mejor rendimiento que FastMember
  • También crea parámetros SqlParameters estructurados para usar como parámetros con valores de tabla de SQL Server

Descargo de responsabilidad : soy el autor de ToDataTable

Rendimiento : amplío algunas pruebas de Benchmark .Net y las incluí en el repositorio ToDataTable . Los resultados fueron los siguientes:

Crear una tabla de datos de 100,000 filas :

                           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

El método FastMember sugerido en la respuesta de Marc parecía funcionar peor que la respuesta de Mary que usaba la reflexión, pero hice otro método usando un FastMember TypeAccessory funcionó mucho mejor. Sin embargo, el paquete ToDataTable superó al lote.

Chris HG
fuente
1

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

Imports System.Reflection
''' <summary>
''' Convert any List(Of T) to a DataTable with correct column types and converts Nullable Type values to DBNull
''' </summary>

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
Jonathan Roberts
fuente
1

Si tiene propiedades en su clase, esta línea de código está bien.

PropertyDescriptorCollection props =
            TypeDescriptor.GetProperties(typeof(T));

pero si tiene todos los campos públicos, use esto:

public static DataTable ToDataTable<T>(  IList<T> data)
        {
        FieldInfo[] myFieldInfo;
        Type myType = typeof(T);
        // Get the type and fields of FieldInfoClass.
        myFieldInfo = myType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance
            | BindingFlags.Public);

        DataTable dt = new DataTable();
        for (int i = 0; i < myFieldInfo.Length; i++)
            {
            FieldInfo property = myFieldInfo[i];
            dt.Columns.Add(property.Name, property.FieldType);
            }
        object[] values = new object[myFieldInfo.Length];
        foreach (T item in data)
            {
            for (int i = 0; i < values.Length; i++)
                {
                values[i] = myFieldInfo[i].GetValue(item);
                }
            dt.Rows.Add(values);
            }
        return dt;
        }

la respuesta original es de arriba, acabo de editar para usar campos en lugar de propiedades

y para usarlo haga esto

 DataTable dt = new DataTable();
            dt = ToDataTable(myBriefs);
            gridData.DataSource = dt;
            gridData.DataBind();
masoud cheragee
fuente
1

Para convertir una lista genérica a una tabla de datos, puede usar el DataTableGenerator

Esta biblioteca le permite convertir su lista en una tabla de datos con funciones múltiples como

  • Traducir encabezado de tabla de datos
  • especifique alguna columna para mostrar
Majid
fuente
1
  private DataTable CreateDataTable(IList<T> item)
        {
            Type type = typeof(T);
            var properties = type.GetProperties();

            DataTable dataTable = new DataTable();
            foreach (PropertyInfo info in properties)
            {
                dataTable.Columns.Add(new DataColumn(info.Name, Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType));
            }

            foreach (T entity in item)
            {
                object[] values = new object[properties.Length];
                for (int i = 0; i < properties.Length; i++)
                {
                    values[i] = properties[i].GetValue(entity);
                }

                dataTable.Rows.Add(values);
            }
            return dataTable;
        }
Maghalakshmi Saravana
fuente
1

Para convertir una lista genérica en DataTable

usando Newtonsoft.Json;

public DataTable GenericToDataTable(IList<T> list)
{
    var json = JsonConvert.SerializeObject(list);
    DataTable dt = (DataTable)JsonConvert.DeserializeObject(json, (typeof(DataTable)));
    return dt;
}
Maghalakshmi Saravana
fuente
0

Esta es la aplicación de consola simple para convertir List a 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;
        }
    }
}
Karthikeyan P
fuente
0

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<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;
    }
Luis Rodrigo Carrasco Lagos
fuente
3
Bienvenido a Stack Overflow . Este es un sitio web de habla inglesa, así que por favor escriba sus respuestas también en inglés.
Hada
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
JoshYates1980
fuente
0

Creo que es más conveniente y fácil de usar.

   List<Whatever> _lobj= new List<Whatever>(); 
    var json = JsonConvert.SerializeObject(_lobj);
                DataTable dt = (DataTable)JsonConvert.DeserializeObject(json, (typeof(DataTable)));
Majedur Rahaman
fuente
0

Lista / datos = nueva Lista (); var dataDT = Newtonsoft.Json.JsonConvert.DeserializeObject (Newtonsoft.Json.JsonConvert.SerializeObject (datos));

usuario12815245
fuente
0

Si desea utilizar la reflexión y establecer el orden de las columnas / incluir solo algunas columnas / Excluir algunas columnas, intente esto:

        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++;
        }
    }
Ahmed_mag
fuente