如何将List<T>转换为DataSet?

18

给定一个对象列表,我需要将其转换为数据集,其中列表中的每个项都由一行表示,每个属性都是行中的列。然后,此数据集将传递给Aspose.Cells函数以创建Excel文档作为报告。

假设我有以下内容:

public class Record
{
   public int ID { get; set; }
   public bool Status { get; set; }
   public string Message { get; set; }
}

给定一个列表 records,如何将其转换为以下的 DataSet:

ID Status Message
1  true   "message" 
2  false  "message2" 
3  true   "message3" 
...

目前我能想到的唯一方法是:

DataSet ds = new DataSet
ds.Tables.Add();
ds.Tables[0].Add("ID", typeof(int));    
ds.Tables[0].Add("Status", typeof(bool));
ds.Tables[0].Add("Message", typeof(string));

foreach(Record record in records)
{
    ds.Tables[0].Rows.Add(record.ID, record.Status, record.Message);
}

但是这种方式让我想到一定有更好的方法,因为至少如果Record添加了新属性,它们不会显示在DataSet中...但同时它允许我控制将每个属性添加到行的顺序。

有人知道更好的方法吗?

5个回答

34
你可以通过反射和泛型来实现,检查底层类型的属性。
考虑使用以下扩展方法:
    public static DataTable ToDataTable<T>(this IEnumerable<T> collection)
    {
        DataTable dt = new DataTable("DataTable");
        Type t = typeof(T);
        PropertyInfo[] pia = t.GetProperties();

        //Inspect the properties and create the columns in the DataTable
        foreach (PropertyInfo pi in pia)
        {
            Type ColumnType = pi.PropertyType;
            if ((ColumnType.IsGenericType))
            {
                ColumnType = ColumnType.GetGenericArguments()[0];
            }
            dt.Columns.Add(pi.Name, ColumnType);
        }

        //Populate the data table
        foreach (T item in collection)
        {
            DataRow dr = dt.NewRow();
            dr.BeginEdit();
            foreach (PropertyInfo pi in pia)
            {
                if (pi.GetValue(item, null) != null)
                {
                    dr[pi.Name] = pi.GetValue(item, null);
                }
            }
            dr.EndEdit();
            dt.Rows.Add(dr);
        }
        return dt;
    }

好的,社区已经表态了,所以我会投票支持这个答案,尽管我不能用它来满足我的需求,因为我想要能够控制参数的顺序。但我一定会记住这个解决方案... - mezoid
2
嘿,我刚刚测试了你的扩展,并发现如果您想控制列在数据表中出现的顺序,则需要按照您希望它们出现的顺序在传递给扩展的T类型对象中声明它们。太棒了! - mezoid

2

我在Microsoft论坛上找到了这段代码。 这是目前最简单易懂、易于理解和使用的方式之一。这节省了我几个小时。我将其定制为扩展方法,而没有对实际实现进行任何更改。下面是代码。它不需要太多解释。

您可以使用两个函数签名和相同的实现

1)public static DataSet ToDataSetFromObject(this object dsCollection)

2)public static DataSet ToDataSetFromArrayOfObject( this object[] arrCollection)。我将在下面的示例中使用这个。

// <summary>
// Serialize Object to XML and then read it into a DataSet:
// </summary>
// <param name="arrCollection">Array of object</param>
// <returns>dataset</returns>

public static DataSet ToDataSetFromArrayOfObject( this object[] arrCollection)
{
    DataSet ds = new DataSet();
    try {
        XmlSerializer serializer = new XmlSerializer(arrCollection.GetType);
        System.IO.StringWriter sw = new System.IO.StringWriter();
        serializer.Serialize(sw, dsCollection);
        System.IO.StringReader reader = new System.IO.StringReader(sw.ToString());
        ds.ReadXml(reader);
    } catch (Exception ex) {
        throw (new Exception("Error While Converting Array of Object to Dataset."));
    }
    return ds;
}

要在代码中使用此扩展

Country[] objArrayCountry = null;
objArrayCountry = ....;// populate your array
if ((objArrayCountry != null)) {
    dataset = objArrayCountry.ToDataSetFromArrayOfObject();
}

你测试过这个代码吗?显然它无法编译。 - Minh Nguyen
请注意,对象需要可序列化才能使用此功能! - Minh Nguyen
Minh,很抱歉听到它对你不起作用。我已经广泛使用了这种方法来进行我的项目迁移。自那时以来,我已经转向了不同的技术。我无法确定Microsoft框架的最新更改是否破坏了它,还是我的代码有问题。 - kumar chandraketu
“dsCollection” 变量在哪里?应该是 “arrCollection”。同时,“arrCollection.GetType” 缺少括号,正确的写法为 “arrCollection.GetType()”。 - Minh Nguyen

1
除了另外使用“反射”来确定类“Record”的属性以处理添加新属性之外,那就差不多了。

你可能是对的...尽管这不是我想听到的。我猜我需要提高我的数据集知识,或者微软的某个人需要想出更好的方法。 - mezoid

0

我对CMS的扩展方法进行了一些更改,以处理List包含原始或String元素的情况。在这种情况下,生成的DataTable将只有一个Column,每个值在列表中都有一个Row

起初,我考虑包括所有值类型(不仅仅是原始类型),但我不想包括结构体(它们是值类型)。

这个变化源于我需要将List(Of Long)List<long>转换为DataTable,以将其用作MS SQL 2008存储过程中的表值参数。

很抱歉我的代码是VB,尽管这个问题标记为;我的项目是VB(不是我的选择),但在c#中应用这些更改不应该很难。

Imports System.Runtime.CompilerServices
Imports System.Reflection

Module Extensions

    <Extension()>
    Public Function ToDataTable(Of T)(ByVal collection As IEnumerable(Of T)) As DataTable
        Dim dt As DataTable = New DataTable("DataTable")
        Dim type As Type = GetType(T)
        Dim pia() As PropertyInfo = type.GetProperties()

        ' For a collection of primitive types create a 1 column DataTable
        If type.IsPrimitive OrElse type.Equals(GetType(String)) Then
            dt.Columns.Add("Column", type)
        Else
            ' Inspect the properties and create the column in the DataTable
            For Each pi As PropertyInfo In pia
                Dim ColumnType As Type = pi.PropertyType
                If ColumnType.IsGenericType Then
                    ColumnType = ColumnType.GetGenericArguments()(0)
                End If
                dt.Columns.Add(pi.Name, ColumnType)
            Next

        End If

        ' Populate the data table
        For Each item As T In collection
            Dim dr As DataRow = dt.NewRow()
            dr.BeginEdit()
            ' Set item as the value for the lone column on each row
            If type.IsPrimitive OrElse type.Equals(GetType(String)) Then
                dr("Column") = item
            Else
                For Each pi As PropertyInfo In pia
                    If pi.GetValue(item, Nothing) <> Nothing Then
                        dr(pi.Name) = pi.GetValue(item, Nothing)
                    End If
                Next
            End If
            dr.EndEdit()
            dt.Rows.Add(dr)
        Next
        Return dt
    End Function

End Module

0

我自己编写了一个小型库来完成这个任务。它仅在第一次将对象类型转换为数据表时使用反射。它会发出一个方法,该方法将完成所有工作并翻译对象类型。

它非常快速。您可以在此处找到它:GoogleCode上的ModelShredder


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