使用反射从类创建DataTable?

47

我刚学习了泛型,想知道是否可以使用它来从我的类动态构建数据表。

或者我可能理解有误。这是我的代码,我想做的是从我的现有类创建一个数据表并填充它。然而我的思路卡住了。

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 类来填充数据?


好的,所以你实际上没有使用泛型,那么你遇到了什么错误? - Mike Perrenoud
我不是?哎呀。错误2:'Generics.Program.CreateDataTable(System.Type)'的最佳重载方法匹配有一些无效参数
错误3:参数1:无法将'Generics.Dog'转换为'System.Type'
错误1:'Generics.Dog'是一个'type',但像一个'variable'使用
- vwdewaal
2
泛型 + 数据库 = 实体框架。请不要重复造轮子。 - Federico Berasategui
好的,我可能理解泛型有些错误,但是我仍在尝试根据现有类创建动态数据表。 - vwdewaal
9个回答

113

在之前所有回答的基础上,这里提供一种可以从任何集合创建DataTable的版本:

public static DataTable CreateDataTable<T>(IEnumerable<T> list)
{
    Type type = typeof(T);
    var properties = type.GetProperties();      
    
    DataTable dataTable = new DataTable();
    dataTable.TableName = typeof(T).FullName;
    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;
}

3
太好了,我已经一年没碰那段代码了,但这正好赶上我正在做的另一件事情。谢谢David。 - vwdewaal
1
修改了代码以包含一个建议的编辑,使得该代码支持可空类型。 - David Airapetyan
1
如何对值执行相同的操作留给读者自己练习 :) - David Airapetyan
对我来说,一个小但值得的改进是添加以下代码行:dataTable.TableName = typeof(T).FullName; 这将表名设置为类型名称。在加入这个代码行之前,在调试器中检查结果时,当dataTable显示为“{}”时感到担忧 - 以为它是空的。然后意识到列和行已经在其中了。 - Paul Evans
很棒的调整,谢谢@Paul Evans,已添加。 - David Airapetyan
显示剩余4条评论


9

我最喜欢的自制函数。它可以同时创建和填充对象,并支持任何类型的对象。

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

5
错误可以通过更改以下内容来解决:
dogTable = CreateDataTable(Dog);

转换为:

dogTable = CreateDataTable(typeof(Dog));

但是您所尝试做的事情有一些注意事项。 首先,DataTable 不能存储复杂类型,因此,如果 Dog 实例上有一个 Cat 实例,您将无法将其作为列添加到 DataTable 中。 在这种情况下,您可以自行决定如何处理它,但请记住这一点。

其次,我建议只有在构建不知道其消费的数据内容时才使用 DataTable。 这样做有其合理用途(例如用户驱动的数据挖掘工具)。 如果您已经有了 Dog 实例中的数据,请直接使用它。

另外还有一点需要注意:

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

可以压缩为以下内容:
DataTable dogTable = CreateDataTable(Dog);

3

这里是稍微修改过的代码,修复了datetime字段的时区问题:

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

1
这是一个VB.Net版本的代码,它可以从作为对象传递给函数的泛型列表中创建数据表。还有一个辅助函数(ObjectToDataTable),可以从对象创建数据表。

导入System.Reflection

   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

0

你可以将对象转换为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

        }      
    }
}

看下面的例子这里


你应该解释一下你在代码中做了什么或者改变了什么。 - clinomaniac

0

使用@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();
        }

0
如果您想设置列的顺序/仅包含某些列/排除某些列,请尝试以下方法:
        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++;
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接