Использование Reflection для создания DataTable из класса?

Я только что узнал об Generics, и мне интересно, смогу ли я использовать его для динамического создания таблиц данных из моих классов.

Или я мог упустить суть здесь. Вот мой код, я пытаюсь создать таблицу данных из моего существующего класса и заполнить ее. Однако я застреваю в своем мыслительном процессе.

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

namespace Generics
{
    public class Dog
    {
        public string Breed { get; set; }
        public string Name { get; set; }
        public int legs { get; set; }
        public bool tail { get; set; }
    }

    class Program
    {
        public static DataTable CreateDataTable(Type animaltype)
        {
            DataTable return_Datatable = new DataTable();
            foreach (PropertyInfo info in animaltype.GetProperties())
            {
                return_Datatable.Columns.Add(new DataColumn(info.Name, info.PropertyType));
            }
            return return_Datatable;
        }

        static void Main(string[] args)
        {
            Dog Killer = new Dog();
            Killer.Breed = "Maltese Poodle";
            Killer.legs = 3;
            Killer.tail = false;
            Killer.Name = "Killer";

            DataTable dogTable = new DataTable();
            dogTable = CreateDataTable(Dog);
//How do I continue from here?


        }      
    }
}    

Сейчас на DataTable указать ошибки. Кроме того, будучи новичком в рефлексии и обобщениях, как я буду на самом деле заполнять данные классом Killer?

8 ответов

Решение

Основываясь на всех предыдущих ответах, вот версия, которая создает DataTable из любой коллекции:

public static DataTable CreateDataTable<T>(IEnumerable<T> list)
{
    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 list)
    {
        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;
}

Вот более компактная версия ответа Дэвида, который также является функцией расширения. Я разместил код в проекте C# на Github.

public static class Extensions
{
    public static DataTable ToDataTable<T>(this IEnumerable<T> self)
    {
        var properties = typeof(T).GetProperties();

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

        foreach (var entity in self)
            dataTable.Rows.Add(properties.Select(p => p.GetValue(entity)).ToArray());

        return dataTable;
    }     
}

Я обнаружил, что это очень хорошо работает в сочетании с кодом для записи DataTable в CSV.

Моя любимая домашняя функция. это создает и заполняет все одновременно. бросить любой предмет.

 public static DataTable ObjectToData(object o)
 {
    DataTable dt = new DataTable("OutputData");

    DataRow dr = dt.NewRow();
    dt.Rows.Add(dr);

    o.GetType().GetProperties().ToList().ForEach(f =>
    {
        try
        {
            f.GetValue(o, null);
            dt.Columns.Add(f.Name, f.PropertyType);
            dt.Rows[0][f.Name] = f.GetValue(o, null);
        }
        catch { }
    });
    return dt;
 }

Ошибка может быть исправлена ​​путем изменения этого:

dogTable = CreateDataTable(Dog);

к этому:

dogTable = CreateDataTable(typeof(Dog));

Но есть некоторые предостережения относительно того, что вы пытаетесь сделать. Первый DataTable не может хранить сложные типы, поэтому если Dog имеет экземпляр Cat на нем вы не сможете добавить это как столбец. Это зависит от вас, что вы хотите сделать в этом случае, но имейте это в виду.

Во-вторых, я бы порекомендовал единственный раз, когда вы используете DataTable это когда вы создаете код, который ничего не знает о данных, которые он потребляет. Для этого есть допустимые варианты использования (например, управляемый пользователем инструмент анализа данных). Если у вас уже есть данные в Dog Например, просто используйте его.

Еще один маленький кусочек, это:

DataTable dogTable = new DataTable();
dogTable = CreateDataTable(Dog);

можно сжать до этого:

DataTable dogTable = CreateDataTable(Dog);

Вот немного измененный код, который исправил проблему часового пояса для полей времени данных:

    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++)
            {
                if (props[i].PropertyType == typeof(DateTime))
                {
                    DateTime currDT = (DateTime)props[i].GetValue(item);
                    values[i] = currDT.ToUniversalTime();
                }
                else
                {
                    values[i] = props[i].GetValue(item);
                }
            }
            table.Rows.Add(values);
        }
        return table;
    }

Вот версия VB.Net, которая создает таблицу данных из общего списка, переданного функции как объект. Существует также вспомогательная функция (ObjectToDataTable), которая создает таблицу данных из объекта.

Система импорта. Отражение

   Public Shared Function ListToDataTable(ByVal _List As Object) As DataTable

    Dim dt As New DataTable

    If _List.Count = 0 Then
        MsgBox("The list cannot be empty. This is a requirement of the ListToDataTable function.")
        Return dt
    End If

    Dim obj As Object = _List(0)
    dt = ObjectToDataTable(obj)
    Dim dr As DataRow = dt.NewRow

    For Each obj In _List

        dr = dt.NewRow

        For Each p as PropertyInfo In obj.GetType.GetProperties

            dr.Item(p.Name) = p.GetValue(obj, p.GetIndexParameters)

        Next

        dt.Rows.Add(dr)

    Next

    Return dt

End Function

Public Shared Function ObjectToDataTable(ByVal o As Object) As DataTable

    Dim dt As New DataTable
    Dim properties As List(Of PropertyInfo) = o.GetType.GetProperties.ToList()

    For Each prop As PropertyInfo In properties

        dt.Columns.Add(prop.Name, prop.PropertyType)

    Next

    dt.TableName = o.GetType.Name

    Return dt

End Function

Вы можете преобразовать объект в xml, затем загрузить документ xml в набор данных, а затем извлечь первую таблицу из набора данных. Однако я не понимаю, насколько это практично, так как это подразумевает создание потоков, наборов данных и таблиц данных и использование диалогов для создания XML-документа.

Я думаю, для доказательства концепции я могу понять, почему. Вот пример, но несколько не решается его использовать.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
using System.Xml.Serialization;

namespace Generics
{
public class Dog
{
    public string Breed { get; set; }
    public string Name { get; set; }
    public int legs { get; set; }
    public bool tail { get; set; }
}

class Program
{
    public static DataTable CreateDataTable(Object[] arr)
    {
        XmlSerializer serializer = new XmlSerializer(arr.GetType());
        System.IO.StringWriter sw = new System.IO.StringWriter();
        serializer.Serialize(sw, arr);
        System.Data.DataSet ds = new System.Data.DataSet();
        System.Data.DataTable dt = new System.Data.DataTable();
        System.IO.StringReader reader = new System.IO.StringReader(sw.ToString());

        ds.ReadXml(reader);
        return ds.Tables[0];
    }

    static void Main(string[] args)
    {
        Dog Killer = new Dog();
        Killer.Breed = "Maltese Poodle";
        Killer.legs = 3;
        Killer.tail = false;
        Killer.Name = "Killer";

        Dog [] array_dog = new Dog[5];
        Dog [0] = killer;
        Dog [1] = killer;
        Dog [2] = killer;
        Dog [3] = killer;
        Dog [4] = killer;

        DataTable dogTable = new DataTable();
        dogTable = CreateDataTable(array_dog);

        // continue here

        }      
    }
}

посмотрите следующий пример здесь

Если вы хотите установить порядок столбцов / включить только некоторые столбцы / исключить некоторые столбцы, попробуйте следующее:

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

Используя ответ, предоставленный @neoistheone, я изменил следующие разделы. Работает нормально сейчас.

DataTable dogTable = new DataTable();
        dogTable = CreateDataTable(typeof(Dog));

        dogTable.Rows.Add(Killer.Breed, Killer.Name,Killer.legs,Killer.tail);

        foreach (DataRow row in dogTable.Rows)
        {
            Console.WriteLine(row.Field<string>("Name") + " " + row.Field<string>("Breed"));
            Console.ReadLine();
        }
Другие вопросы по тегам