Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

epplus using LoadFromCollection with anonymous types

Tags:

c#

epplus

I have a IEnumerable<object> dataSource which contains a collection anonymous types. The actual structure of the anonymous type won't be known at design time, so I'm trying to find a generic solution that can handle any anonymous type.

How can I load them into epplus to create a spreadsheet? I have a worksheet called ws and I tried:

ws.Cells["A1"].LoadFromCollection(dataSource, true);

However when that runs it outputs all of the anonymous type's properties into a single cell:

{ Id = 10000, Title = This is a test }

I've tried passing in MemberInfo using:

var members = dataSource.First().GetType().GetMembers();
ws.Cells["A1"].LoadFromCollection(this._dataSource, true,
    TableStyles.Medium1, BindingFlags.Public, members);

But that throws an exception:

Supplied properties in parameter Properties must be of the same type as T

Any suggestions on how I can create a spreadsheet using anonymous types in c#?

like image 411
Tony Avatar asked Jul 02 '14 14:07

Tony


2 Answers

I have tested

using (var excel = new OfficeOpenXml.ExcelPackage())
{
    var sheet = excel.Workbook.Worksheets.Add("Test");
    sheet.Cells["A1"].LoadFromCollection(dataSource, true);
    excel.SaveAs(new FileInfo(@"C:\Temp\Test.xlsx"));
}

with this sample data:

var dataSource = Enumerable.Range(1, 100).Select(i => new{ ID=i, Title="Title " + i });

It works fine. It creates two columns with the correct headers and 100 rows.

But you should use anonymous types only if you know the structure at compile time.

You could use a DataTable and LoadFromDataTable instead. Since i don't know how you create the anonymous type i show you just a small sample:

DataTable dataSource = new DataTable();
dataSource.Columns.Add("Id");    // default type is string 
dataSource.Columns.Add("Title");  
// add other columns
dataSource.Rows.Add("1", "Title1");
// add other rows

using (var excel = new OfficeOpenXml.ExcelPackage())
{
    var sheet = excel.Workbook.Worksheets.Add("Test");
    sheet.Cells["A1"].LoadFromDataTable(dataSource, true);
    excel.SaveAs(new FileInfo(@"C:\Temp\Test.xlsx"));
}
like image 171
Tim Schmelter Avatar answered Sep 21 '22 02:09

Tim Schmelter


You could group the anonymous types to make it easier for exporting with dataTables. The bug "Supplied properties in parameter Properties must be of the same type as T" is still there and a workaround is using DataTables.

// Imagine list is your main datasource
IEnumerable<object> list = Enumerable.Empty<object>(); // Data Source of <object>

// Added anon types at runtime added to the object list
var anonTypesOne = new object[] 
{ 
    new { GuidID = Guid.NewGuid(), StringProperty = "the string property" },
    new { IntegerID = 1, IntegerProperty = 99 }
};

var anonTypesTwo = new object[]
{
    new { StringID = "1", BooleanProperty = true, NumberProperty = 3, StringProperty = "Four" },
    new { GuidID = Guid.NewGuid(), NumberThree = 3 },
    new { GuidID = Guid.NewGuid(), NumberThree = 3 },
    new { GuidID = Guid.NewGuid(), NumberThree = 3 }
};

list = list.Concat(anonTypesOne).Concat(anonTypesTwo);

// Grouping works on anon types so we can group the export into their own tables
var groupings = list.GroupBy(i => i.GetType());

using(var package = new ExcelPackage(new FileInfo("C:\\Temp\\Anon.xlsx")))
{
    var ws = package.Workbook.Worksheets.Add("Anonymous Types");

    // add each "anon type matched grouping"
    foreach(var grouping in groupings)
    {
        var isNew = ws.Dimension == null; // the sheet is empty if Dimension is null.
        var row = 0;

        if(isNew)
        {
            row = 1; // start from the first row
        }
        else 
        {       
            // otherwise there are tables already, start from the bottom
            row = ws.Dimension.End.Row; 
        }       

        // because of EPP inheritance bug of T, we can just use dataTable
        DataTable dt = new DataTable(grouping.Key.Name);
        var properties = grouping.Key.GetProperties(); // Get anon type Properties

        foreach(var property in properties)
        {
            dt.Columns.Add(property.Name);
        }

        foreach(var item in grouping.ToList())
        {
            var dataRow = dt.NewRow();

            foreach(var p in properties) // populate a single row
            {
                dataRow[p.Name] = p.GetValue(item); // item is anon object instance
            }

            dt.Rows.Add(dataRow);
        }

        if(isNew) // load into the top most left cell of the worksheet
            ws.Cells[1, 1].LoadFromDataTable(dt, PrintHeaders: true);
        else // load from the dimension of current items + 1 row for spacing
            ws.Cells[ws.Dimension.End.Row + 1, 1].LoadFromDataTable(dt, PrintHeaders: true);

        ws.InsertRow(ws.Dimension.End.Row + 2, 5); // Insert some padding between each group

    }

    package.Save();
}
like image 41
Patrick Magee Avatar answered Sep 22 '22 02:09

Patrick Magee