17

Given a list of objects, I am needing to transform it into a dataset where each item in the list is represented by a row and each property is a column in the row. This DataSet will then be passed to an Aspose.Cells function in order to create an Excel document as a report.

Say I have the following:

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

Given a List records, how do I transform it into a DataSet as follows:

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

At the moment the only thing I can think of is as follows:

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

But this way leaves me thinking there must be a better way since at the very least if new properties are added to Record then they won't show up in the DataSet...but at the same time it allows me to control the order each property is added to the row.

Does anyone know of a better way to do this?

mezoid
  • 28,090
  • 37
  • 107
  • 148

5 Answers5

33

You can do it through reflection and generics, inspecting the properties of the underlying type.

Consider this extension method that I use:

    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;
    }
Christian C. Salvadó
  • 807,428
  • 183
  • 922
  • 838
  • Well, the community has spoken so I'll vote this one the answer even though I won't be able to use it for my purposes since I want to be able control the order of the parameters. But I'll definitely keep this solution in mind... – mezoid Feb 08 '09 at 10:24
  • 2
    Hey, I've just tested your extension and have found that if you want to control the order the columns appear in the datatable then you need to declare them in the order you want them in the object of type T that you pass to the extension. That's awesome! – mezoid Feb 13 '09 at 00:08
2

I found this code on Microsoft forum. This is so far one of easiest way, easy to understand and use. This has saved me hours. I have customized this as extension method without any change to actual implementaion. Below is the code. it doesn't require much explanation.

You can use two function signature with same implementation

1) public static DataSet ToDataSetFromObject(this object dsCollection)

2) public static DataSet ToDataSetFromArrayOfObject( this object[] arrCollection). I'll be using this one in below example.

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

To use this extension in code

Country[] objArrayCountry = null;
objArrayCountry = ....;// populate your array
if ((objArrayCountry != null)) {
    dataset = objArrayCountry.ToDataSetFromArrayOfObject();
}
kumar chandraketu
  • 2,232
  • 2
  • 20
  • 25
  • Have you tested this as it obviously does not compile? – Minh Nguyen Jun 11 '21 at 13:01
  • Note that the object need to be serializable to be able to use this! – Minh Nguyen Jun 11 '21 at 17:18
  • Minh, sorry to hear its not working for you. I have used this method extensively for my project migration. Since then i have moved to different technology. I can't say whether latest changes in Microsoft framework is breaking it or my code has issue. – kumar chandraketu Jun 11 '21 at 17:40
  • for ex, where is the "dsCollection" variable? it should be arrCollection. Also arrCollection.GetType is missing the bracket it should be arrCollection.GetType() – Minh Nguyen Jun 11 '21 at 23:13
1

Apart from additionally using Reflection to determine the properties of class Record to take care of adding new properties, that's pretty much it.

Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
  • You're probably right...even though its not what I wanted to hear. I guess either I need to improve my knowlege of DataSets or someone at Microsoft needs to think of a better way. – mezoid Feb 08 '09 at 10:26
0

I've written a small library myself to accomplish this task. It uses reflection only for the first time an object type is to be translated to a datatable. It emits a method that will do all the work translating an object type.

Its blazing fast. You can find it here: ModelShredder on GoogleCode

Johannes Rudolph
  • 35,298
  • 14
  • 114
  • 172
0

I made some changes to CMS' extension method to handle the case when the List contains primitive or String elements. In that case the resulting DataTable will only have one Column with a Row for each of the values in the list.

At first I thought of including all value types (not only primitive types) but I didn't want Structures (which are value types) to be included.

This change arose from my need of converting a List(Of Long), or List<long>, into a DataTable to use it as a Table-Valued Parameter in a MS SQL 2008 Stored Procedure.

I'm sorry my code is in VB even though this question is tagged ; my project is in VB (NOT my choice) and it shouldn't be hard to apply the changes in 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
Tlatoani
  • 138
  • 1
  • 4