使用反射导出对象列表到Excel,并且支持统计
最近项目中需要导出Excel报表,由于用到的是Linq,输出数据一般都是List<object>。例如:
var sum = from l in downloadlist join u in uselist on l.ID equals u.ID into jUL from u in jUL.DefaultIfEmpty() orderby l.Count descending select new DownReportInfo { Name = l.Name, UseCount = u == null ? 0 : u.Count, DownCount = l.Count, Per = u == null ? 0 : (l.Count != 0 ? (int)((double)u.Count / (double)l.Count * 100) : 0), ContentID = l.ID };
几乎每个前端能看到的表格都需要这样导出到Excel中,如果全部依靠手工使用NPOI的话,工作量将会非常大,而且是很枯燥的。那么如何利用自定义属性来生成Excel呢?下面一步步来实现:
首先,我们的Linq返回不能是匿名类,如上面的代码,sum.ToList()后返回List<DownReportInfo>这样的泛型列表。其中DownReportInfo这个类定义如下:
public class DownReportInfo { [DisplayName("名称")] [Formula("总计")] public string Name { get; set; } [DisplayName("下载数量")] [Formula(FormulaType.SUM)] public int DownCount { get; set; } [DisplayName("使用数量")] [Formula(FormulaType.SUM)] public int UseCount { get; set; } [DisplayName("使用率")] [Formula(FormulaType.AVG)] public int Per { get; set; } public int AppID { get; set; } public int ContentID { get; set; } public int FolderID { get; set; } }
两个属性Display与Formula其中Formula是自定义属性(CustomAttribute),继承自Attribute,当实体字段没有这两个属性时,Excel中将不包含此字段
public class FormulaAttribute:Attribute { public FormulaAttribute(FormulaType type) { FormulaType = type; } public FormulaAttribute(string val) { FormulaType=FormulaType.STRING; DisplayString = val; } public string DisplayString { get; set; } public FormulaType FormulaType { get; set; } } public enum FormulaType { SUM, COUNT, AVG, STRING }
其中的枚举FormulaType值,顾名思义分别是总计,计数,平均,最后一个STRING是照样输出。看完全文你可以自由扩展统计类型.
DisplayString是当FormulaType为String时要照原样输出的字符串。
生成Excel的工具类如下:
public class ExcelReportHelper { /// <summary> /// 将列表导出到Excel中 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="listData"></param> /// <param name="sheetName"></param> /// <param name="title"></param> /// <param name="description"></param> /// <param name="saveTo"></param> /// <returns></returns> public string ExportToExcel<T>(List<T> listData, string sheetName, string title, string description,string saveTo) { try { int propertyCount = getPropertyCount(typeof(T)); var hssfWorkbook = new HSSFWorkbook(); var sheet1 = hssfWorkbook.CreateSheet(sheetName); var row0 = (HSSFRow)sheet1.CreateRow(0); row0.CreateCell(0, CellType.STRING); var row = sheet1.CreateRow(0); var cell = row.CreateCell(0); cell.SetCellValue(title); //标题样式 var styleTitle = hssfWorkbook.CreateCellStyle(); styleTitle.Alignment = HorizontalAlignment.CENTER; var font = hssfWorkbook.CreateFont(); font.FontHeight = 20 * 20; styleTitle.SetFont(font); cell.CellStyle = styleTitle; sheet1.AddMergedRegion(new CellRangeAddress(0, 0, 0, propertyCount-1)); row = sheet1.CreateRow(1); cell = row.CreateCell(0); sheet1.AddMergedRegion(new CellRangeAddress(1, 1, 0, propertyCount-1)); cell.SetCellValue(description); //描述样式 var styleDesc = hssfWorkbook.CreateCellStyle(); styleDesc.Alignment = HorizontalAlignment.CENTER; var fontDesc = hssfWorkbook.CreateFont(); fontDesc.Color = HSSFColor.GREY_50_PERCENT.index; styleDesc.SetFont(fontDesc); cell.CellStyle = styleDesc; var row1 = (HSSFRow)sheet1.CreateRow(2); //表头样式 var rowstyle = hssfWorkbook.CreateCellStyle(); rowstyle.Alignment = HorizontalAlignment.CENTER; rowstyle.FillBackgroundColor = HSSFColor.GREY_25_PERCENT.index; var font1 = hssfWorkbook.CreateFont(); font1.FontHeight = 20 * 12; font1.Boldweight = 600; rowstyle.SetFont(font1); WriteHeader(typeof(T), row1,rowstyle); int i = 0; foreach (var item in listData) { int rowIndex = i; var rowData = (HSSFRow)sheet1.CreateRow(rowIndex + 3); WriteData(item, typeof(T),rowData); i++; } setAutoColumn(sheet1,i); var rowfooter = (HSSFRow)sheet1.CreateRow(i+3); WriteFooter(typeof(T), rowfooter,sheet1); var p=Path.GetDirectoryName(saveTo); if (!Directory.Exists(p)) { Directory.CreateDirectory(p); } var xlsname = sheetName+ ".xls"; string save = p + "/" + xlsname; var file = new FileStream(save, FileMode.Create); hssfWorkbook.Write(file); file.Close(); return xlsname; } catch (Exception) { return ""; } } private void setAutoColumn(ISheet sheet,int maxColumn) { //列宽自适应,只对英文和数字有效 for (int i = 0; i <= maxColumn; i++) { sheet.AutoSizeColumn(i); } //获取当前列的宽度,然后对比本列的长度,取最大值 for (int columnNum = 0; columnNum <= maxColumn; columnNum++) { int columnWidth = sheet.GetColumnWidth(columnNum) / 256; for (int rowNum = 3; rowNum <= sheet.LastRowNum; rowNum++) { IRow currentRow; //当前行未被使用过 if (sheet.GetRow(rowNum) == null) { currentRow = sheet.CreateRow(rowNum); } else { currentRow = sheet.GetRow(rowNum); } if (currentRow.GetCell(columnNum) != null) { ICell currentCell = currentRow.GetCell(columnNum); int length = Encoding.Default.GetBytes(currentCell.ToString()).Length; if (columnWidth < length) { columnWidth = length; } } } sheet.SetColumnWidth(columnNum, columnWidth * 256); } } private int getPropertyCount(Type type) { if (type != null) { Type t = type; PropertyInfo[] propertyInfo = t.GetProperties(); int i = 0; foreach (PropertyInfo propInfo in propertyInfo) { object[] objAttrs = propInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true); if (objAttrs.Length > 0) { i++; } } return i; } return 0; } /// <summary> /// 写表头 /// </summary> /// <param name="type"></param> /// <param name="row"> </param> /// <param name="style"> </param> public void WriteHeader(Type type,HSSFRow row,ICellStyle style) { if (type != null) { Type t = type; PropertyInfo[] propertyInfo = t.GetProperties(); int i = 0; foreach (PropertyInfo propInfo in propertyInfo) { var cell = row.CreateCell(i); object[] objAttrs = propInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true); if (objAttrs.Length > 0) { var attr = objAttrs[0] as DisplayNameAttribute; cell.SetCellValue(attr != null ? attr.DisplayName : ""); i++; } cell.CellStyle = style; } } } public void WriteData<T>(T obj,Type type,HSSFRow row) { if (obj != null) { Type t = type; PropertyInfo[] propertyInfo = t.GetProperties(); int i = 0; foreach (PropertyInfo propInfo in propertyInfo) { object[] objAttrs = propInfo.GetCustomAttributes(typeof (DisplayNameAttribute), true); if (objAttrs.Length > 0) { var cell = row.CreateCell(i); object value = propInfo.GetValue(obj, null); if(propInfo.PropertyType==typeof(int)) { cell.SetCellValue((int)value); }else { cell.SetCellValue(value.ToString()); } i++; } } } } private void WriteFooter(Type type,HSSFRow row,ISheet sheet) { Type t = type; PropertyInfo[] propertyInfo = t.GetProperties(); int col = 0; int rownum=row.RowNum; foreach (PropertyInfo propInfo in propertyInfo) { object[] objAttrs = propInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true); if (objAttrs.Length > 0) { object[] formulaAttrs = propInfo.GetCustomAttributes(typeof (FormulaAttribute), true); if(formulaAttrs.Length>0) { string formulaRange = "({0}{1}:{2}{3})"; var colName = ConvertColumnIndexToColumnName(col); formulaRange = string.Format(formulaRange, colName, 4, colName, rownum); var cell = row.CreateCell(col); var value = formulaAttrs[0] as FormulaAttribute; if (value != null) { switch (value.FormulaType) { case FormulaType.STRING: cell.SetCellValue(value.DisplayString); break; case FormulaType.COUNT: cell.SetCellFormula("COUNT"+formulaRange); break; case FormulaType.SUM: cell.SetCellFormula("SUM"+formulaRange); break; case FormulaType.AVG: cell.SetCellFormula("AVERAGE" + formulaRange); break; default: cell.SetCellValue(""); break; } } } col++; } } sheet.ForceFormulaRecalculation = true; } // <summary> /// 将Excel的列索引转换为列名,列索引从0开始,列名从A开始。如第0列为A,第1列为B... /// </summary> /// <param name="index">列索引</param> /// <returns>列名,如第0列为A,第1列为B...</returns> public static string ConvertColumnIndexToColumnName(int index) { index = index + 1; int system = 26; char[] digArray = new char[100]; int i = 0; while (index > 0) { int mod = index % system; if (mod == 0) mod = system; digArray[i++] = (char)(mod - 1 + 'A'); index = (index - 1) / 26; } StringBuilder sb = new StringBuilder(i); for (int j = i - 1; j >= 0; j--) { sb.Append(digArray[j]); } return sb.ToString(); } }
调用非常方便:
var filename = erh.ExportToExcel(json.ToList(), "按内容统计", curdep.Name + " - 下载统计", "从" + datebegin + "到" + dateend + "按内容统计", saveTo);
第一个参数为泛型列表,第二个为Excel的表单名称,第三个为标题,第四个为描述,第五个为Excel保存路径。最终生成的Excel形式如下:
欢迎转载,但请保留链接 http://www.cnblogs.com/limlee
--- GeekLion