简体   繁体   中英

Export iQueryable of complex objects to Excel

We have some code that exports data from a database to Excel and it works well.

The main problem is we use a DB view to gather all our data. This creates the issue of having a sizable view as we have multiple types of objects of which we export.

class Event
  int id
  string title
  List<EventDate> Dates
  string desc

class EventDate
  DateTime start
  DateTime end
  List<EventLocation> Locations

class EventLocation
  string address
  string city
  string zip

class Birthday : Event
  int balloonsOrdered
  string cakeText

class Meeting : Event
  string Organizer
  string Topic

So, above is the model. Birthday and Meeting inherit from Event and all Event objects have a list of EventDate objects. Each EventDate object has a start date, end date and a list of Location objects.

Our goal is to find a dynamic way to get data from the DB to an Excel doc. We'd rather not maintain a massive view in the database (as we'll add more event types eventually).

I'm not all that familiar with .NET's XML capabilities, but the solution we are using now uses OpenXML and the code makes sense.

You could create a CSV file using List<T> and and following code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Web;

public static void CreateCSV<T>(List<T> list, string csvNameWithExt)
{
    if (list == null || list.Count == 0) return;

    HttpContext.Current.Response.Clear();
    HttpContext.Current.Response.AddHeader(
        "content-disposition", string.Format("attachment; filename={0}", csvNameWithExt));
    HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";

    //get type from 0th member
    Type t = list[0].GetType();
    string newLine = Environment.NewLine;

    using (StringWriter sw = new StringWriter())
    {
        //gets all properties
        PropertyInfo[] props = t.GetProperties();

        //this is the header row
        //foreach of the properties in class above, write out properties
        foreach (PropertyInfo pi in props)
        {
            sw.Write(pi.Name.ToUpper() + ",");
        }
        sw.Write(newLine);

        //this acts as datarow
        foreach (T item in list)
        {
            //this acts as datacolumn
            foreach (PropertyInfo Column in props)
            {
                //this is the row+col intersection (the value)
                string value = item.GetType().GetProperty(Column.Name).GetValue(item, null).ToString();
                if (value.Contains(","))
                {
                    value = "\"" + value + "\"";
                }
                sw.Write(value + ",");
            }
            sw.Write(newLine);
        }

        //  render the htmlwriter into the response
        HttpContext.Current.Response.Write(sw.ToString());
        HttpContext.Current.Response.End();
    }
}

I improved the solution from Brad to better work with Entity Framework Data Annotations :

  • it gets the display name from annotations instead of the column name
  • it does not export columns that are marked "scaffold = false" via annotations
  • it offers you a way to deal with complex types (not primitive types)

    public static class ExportListToCSV { public static void CreateCSV<T>(List<T> list, string csvNameWithExt) { if (list == null || list.Count == 0) return; HttpContext.Current.Response.Clear(); HttpContext.Current.Response.AddHeader( "content-disposition", string.Format("attachment; filename={0}", csvNameWithExt)); HttpContext.Current.Response.ContentType = "application/vnd.ms-excel"; //get type from 0th member Type t = list[0].GetType(); string newLine = Environment.NewLine; //gets all columns PropertyInfo[] columns = t.GetProperties(); // skip columns where ScaffoldColumn = false // useful to skip column containing IDs for Foreign Keys var props = t.GetProperties(BindingFlags.Instance | BindingFlags.Public) .Select(p => new { Property = p, Attribute = p.GetCustomAttribute<ScaffoldColumnAttribute>() }) .Where(p => p.Attribute == null || (p.Attribute != null && p.Attribute.Scaffold != false)) .ToList(); using (StringWriter sw = new StringWriter()) { //this is the header row foreach (var prop in props) { var pi = prop.Property; string columnName = ""; // outputs raw column name, but this is not really meaningful for the end user //sw.Write(pi.Name + ","); columnName = pi.GetDisplayName(); sw.Write(columnName + ","); } sw.Write(newLine); //this acts as datarow foreach (T item in list) { //this acts as datacolumn foreach (var prop in props) { var column = prop.Property; //this is the row+col intersection (the value) PropertyInfo info = item.GetType().GetProperty(column.Name); //string value = info.GetValue(item, null); string value = GetDescriptionForComplexObjects(info.GetValue(item, null)); if (value.Contains(",")) { value = "\\"" + value + "\\""; } sw.Write(value + ","); } sw.Write(newLine); } // render the htmlwriter into the response HttpContext.Current.Response.Write(sw.ToString()); HttpContext.Current.Response.End(); } } private static string GetDescriptionForComplexObjects(object value) { string desc = ""; if ( (value != null && !IsSimpleType(value.GetType())) ) { dynamic dynObject = value; if (dynObject != null) { //using Model Extensions, //I made sure all my objects have a DESCRIPTION property //this property must return a string //for an employee object, you would return the employee's name for example //desc = dynObject.DESCRIPTION; } } else { desc = "" + value; } return desc; } public static string GetDisplayName(this PropertyInfo pi) { if (pi == null) { throw new ArgumentNullException(nameof(pi)); } return pi.IsDefined(typeof(DisplayAttribute)) ? pi.GetCustomAttribute<DisplayAttribute>().GetName() : pi.Name; } public static bool IsSimpleType(Type type) { return type.IsPrimitive || new Type[] { typeof(Enum), typeof(String), typeof(Decimal), typeof(DateTime), typeof(DateTimeOffset), typeof(TimeSpan), typeof(Guid) }.Contains(type) || Convert.GetTypeCode(type) != TypeCode.Object || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && IsSimpleType(type.GetGenericArguments()[0])) ; } }

    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM