导出报表,将程序中的list或者dataTable进行组织。然后通过特定的形式,显示到Excel或者word中,方便打印。
目前正在使用的方式,事先用报表设计工具,设置一个模板,然后导出报表的时候,读取模板,然后将模板中的数据进行替换。这也是最常用的一个方式。
我们公司,现在没有使用报表工具,使用的Excel。
使用Excel做报表模板,然后向Excel中写数据,进而达到一个导出报表的功能。
因为有大量的数据需要写入到Excel,标签只是一个标记。
这样就提供了两种写入Excel的方式:
方法一
逐个单元格进行数据传输。程序和Excel相互交互
方法二
将大量有规律的数据,直接传输给Excel,标签只是一个起始位置。程序和Excel交互一次。
测试两种方法时间比较
在6000条数据的测试下,
第一种方案,2min54s
第二种方案,1min22s
很显然,直接把dataTable提交给Excel比一个一个的写单元格要快的多。节省的时间,就是程序和Excel进行数据交互的过程。
导出模板设计思路:
1、写一个Excel模板,在里面写一些标签
2、替换模板中的标签,然后向里面写数据
3、对报表中的数据区域进行格式调整
模板中的行数不固定,列数不固定,所以模板大概形式如下:
然后,把数据,替换到标签中。
在使用第一种方案的时候,导出花费的时间 都在第二步 替换{T$data} 标签上。时间复杂度 O(n2)
所以在导出的数据量过大的时候,慢就算了。而且,这种方案还会报错。
“如果您具有少量的数据,则逐个单元格地传输数据是可以接受的方法。您可以灵活地将数据放到工作簿中的任何地方,并可以在运行时根据条件对单元格进行格式设置。然而,如果您具有大量需要传输到 Excel 工作簿的数据,则使用这种方法不是一个好主意。您在运行时获取的每一个Range
对象都会产生一个接口请求,这意味着数据传输速度会变得较慢。此外,Microsoft Windows 95、Microsoft Windows 98 以及 Microsoft Windows Millennium Edition (Me) 都对接口请求有 64 KB 的限制。如果您具有 64 KB 以上的接口请求,则“自动化”服务器 (Excel) 可能会停止响应,或者您可能会收到指出内存不足的错误信息。”
//读取Excel模板并打开 string execPath = Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath); pathTemplateFile = Path.Combine(Path.Combine(execPath, @"File\"), "Template.xlt"); var myExcel = new Excel.Application { Visible = true, UserControl = true, DisplayAlerts = false, AlertBeforeOverwriting = false }; Excel.Workbooks workbooks = myExcel.Workbooks; Excel._Workbook workbook = workbooks.Add(pathTemplateFile); Excel.Sheets sheets = workbook.Sheets;
第一种方案的实现:
代码实现:
((Excel.Range)worksheet.Rows[rng.Row, missing]).Copy(worksheet.Rows[curRow, missing]); foreach (DataRow dr in tableData.Rows) { // 插入行并复制格式行 rng.EntireRow.Insert(Excel.XlInsertShiftDirection.xlShiftDown); //((Excel.Range)worksheet.Rows[rng.Row, missing]).Copy(worksheet.Rows[curRow, missing]); // 填充行数据 var curCol = rng.Column; foreach (DataColumn dc in tableData.Columns) { worksheet.Cells[curRow, curCol] = dr[dc].ToString(); curCol++; } curRow++; }
第二种方案:
使用Range对象的CopyfromRecordset方法,直接将dataTable放入到标签的位置(标签作为左上角的单元格)
//复制{$Cols}所在行的格式 ((Excel.Range)worksheet.Rows[rng.Row, missing]).Copy(worksheet.Rows[curRow, missing]); for (int j = 0; j < dbRows; j++) { //然后以dataTable 的行数 插入若干行 rng.EntireRow.Insert(Excel.XlInsertShiftDirection.xlShiftDown); } //寻找标签 object findText = "{T$data}"; //获取标签所在位置的Range Excel.Range objRange = worksheet.Rows.Find(find, missing, missing, missing, missing, Excel.XlSearchDirection.xlNext, missing, missing);//获取Range对象 //使用该Range对象,把数据集 直接放到标签的位置 objRange.CopyFromRecordset(rs, Type.Missing, Type.Missing);
其中,rs是ADODB.Recordset对象。
//将数据区域的dataTable转换成Recordset ADODB.Recordset rs = ConvertToRecordset(dtTable); //获取数据区域的行数 int dbRows = dtTable.Rows.Count; //获取数据区域的列数 int dbCols = dtTable.Columns.Count;
注意:CopyFromRecordset 只能与 ADORecordset 对象一起使用。使用
ADO.NET 创建的DataSet 不能与 CopyFromRecordset 方法一起使用。以下几部分中的多个示例演示了如何利用 ADO.NET 向 Excel 传输数据。
所以需要将dataTable转换为Recordset对象。
/// <summary> /// 将Datatable转换成Recordset对象 /// </summary> /// <param name="inTable"></param> /// <returns></returns> public static ADODB.Recordset ConvertToRecordset(DataTable inTable) { ADODB.Recordset result = new ADODB.Recordset(); result.CursorLocation = ADODB.CursorLocationEnum.adUseClient; ADODB.Fields resultFields = result.Fields; System.Data.DataColumnCollection inColumns = inTable.Columns; foreach (DataColumn inColumn in inColumns) { resultFields.Append(inColumn.ColumnName //, TranslateType(inColumn.DataType) , TranslateType(inColumn.DataType) , inColumn.MaxLength , inColumn.AllowDBNull ? ADODB.FieldAttributeEnum.adFldIsNullable : ADODB.FieldAttributeEnum.adFldUnspecified , null); } result.Open(System.Reflection.Missing.Value , System.Reflection.Missing.Value , ADODB.CursorTypeEnum.adOpenStatic , ADODB.LockTypeEnum.adLockOptimistic, 0); foreach (DataRow dr in inTable.Rows) { result.AddNew(System.Reflection.Missing.Value, System.Reflection.Missing.Value); for (int columnIndex = 0; columnIndex < inColumns.Count; columnIndex++) { resultFields[columnIndex].Value = dr[columnIndex]; } } return result; } static ADODB.DataTypeEnum TranslateType(Type columnType) { switch (columnType.UnderlyingSystemType.ToString()) { case "System.Boolean": return ADODB.DataTypeEnum.adBoolean; case "System.Byte": return ADODB.DataTypeEnum.adUnsignedTinyInt; case "System.Char": return ADODB.DataTypeEnum.adChar; case "System.DateTime": return ADODB.DataTypeEnum.adDate; case "System.Decimal": return ADODB.DataTypeEnum.adCurrency; case "System.Double": return ADODB.DataTypeEnum.adDouble; case "System.Int16": return ADODB.DataTypeEnum.adSmallInt; case "System.Int32": return ADODB.DataTypeEnum.adInteger; case "System.Int64": return ADODB.DataTypeEnum.adBigInt; case "System.SByte": return ADODB.DataTypeEnum.adTinyInt; case "System.Single": return ADODB.DataTypeEnum.adSingle; case "System.UInt16": return ADODB.DataTypeEnum.adUnsignedSmallInt; case "System.UInt32": return ADODB.DataTypeEnum.adUnsignedInt; case "System.UInt64": return ADODB.DataTypeEnum.adUnsignedBigInt; case "System.String": default: return ADODB.DataTypeEnum.adVarChar; } }
在以上两种方法中,第一种方法,写入数据慢,原因就是程序与Excel交互太过频繁。所以减少两个进程之间的交互,才能缩短导出时间。