ExcelUtility 類庫經過我(夢在旅途)近期不斷的優化與新增功能,現已基本趨向穩定,功能上也基本可以滿足絕大部份的EXCEL導出需求,該類庫已在我們公司大型ERP系統全面使用,效果不錯,今天應用戶的特殊需求,我又新增了一個功能,導出時動態生成多Sheet EXCEL。
新增方法一:由GetFormatterContainer Func委托導出基於EXCEL模板的多Sheet文件,方法定義如下:
/// <summary>
/// 由GetFormatterContainer Func委托導出基於EXCEL模板的多工作薄文件
/// </summary>
/// <typeparam name="T">數據源可枚舉項類型</typeparam>
/// <param name="templatePath">模板路徑</param>
/// <param name="sheetName">模板中使用的工作薄名稱</param>
/// <param name="dataSource">數據源</param>
/// <param name="getFormatterContainer">生成模板數據格式化容器(SheetFormatterContainer)委托,在委托方法中實現模板的格式化過程</param>
/// <param name="sheetSize">每個工作薄顯示的數據記錄數</param>
/// <param name="filePath">導出路徑,可選</param>
/// <returns></returns>
public static string ToExcelWithTemplate<T>(string templatePath, string sheetName, IEnumerable<T> dataSource, Func<IEnumerable<T>, SheetFormatterContainer> getFormatterContainer, int sheetSize, string filePath = null)
{
if (!File.Exists(templatePath))
{
throw new FileNotFoundException(templatePath + "文件不存在!");
}
bool isCompatible = Common.GetIsCompatible(templatePath);
if (string.IsNullOrEmpty(filePath))
{
filePath = Common.GetSaveFilePath(isCompatible);
}
else if (isCompatible && !Path.GetExtension(filePath).Equals(".xls", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("當模板采用兼容模式時(低版本格式,如:xls,xlt),則指定的導出文件格式必需為xls。");
}
if (string.IsNullOrEmpty(filePath)) return null;
int sheetCount = 0;
var formatterContainers = new Dictionary<string, SheetFormatterContainer>();
IEnumerable<T> data = null;
while ((data = dataSource.Take(sheetSize)).Count() > 0)
{
var sheetFormatterContainer = getFormatterContainer(data);
sheetCount++;
if (sheetCount == 1)
{
formatterContainers.Add(sheetName, sheetFormatterContainer);
}
else
{
formatterContainers.Add(sheetName + sheetCount.ToString(), sheetFormatterContainer);
}
dataSource = dataSource.Skip(sheetSize);
}
string temp_templatePath = null;
try
{
temp_templatePath = Common.CreateTempFileByTemplate(templatePath, sheetName, sheetCount);
filePath = ToExcelWithTemplate(temp_templatePath, formatterContainers, filePath);
}
finally
{
if (!string.IsNullOrEmpty(temp_templatePath) && File.Exists(temp_templatePath))
{
File.Delete(temp_templatePath);
}
string temp_templateConfigFilePath = Path.ChangeExtension(temp_templatePath, ".xml");
if (File.Exists(temp_templateConfigFilePath))
{
File.Delete(temp_templateConfigFilePath);
}
}
return filePath;
}
簡要說明上述方法實現原理步驟:
1.指定模板路徑、初始工作薄名稱、導出的數據源、每個工作薄顯示的記錄數、封裝生成模板數據格式化容器(SheetFormatterContainer)委托,在委托方法中實現模板的格式化過程;
2.依據每個工作薄顯示的記錄數,循環拆分數據源,並計算出需要的工作薄總數以及生成模板數據格式化容器字典(Key:Sheet名稱,Value:模板數據格式化容器對象);
3.生成2中計算的所需的工作薄的臨時模板文件(存放在系統的本地臨時目錄:Temp)
4.調用ToExcelWithTemplate的其它重載方法(基於模板+多sheet生成EXCEL方法)來完成EXCEL的導出;
5.無論最終導出成功與否,將刪除臨時模板及臨時模板配置文件;
測試示例代碼如下:
/// <summary>
/// 測試方法:測試依據模板+DataTable來生成多工作薄的EXCEL
/// </summary>
[TestMethod]
public void TestExportToExcelWithTemplateByDataTable2()
{
DataTable dt = GetDataTable();
string templateFilePath = AppDomain.CurrentDomain.BaseDirectory + "/excel.xls"; //獲得EXCEL模板路徑
string excelPath = ExcelUtility.Export.ToExcelWithTemplate<DataRow>(templateFilePath, "table", dt.Select(), (data) =>
{
SheetFormatterContainer formatterContainers = new SheetFormatterContainer(); //實例化一個模板數據格式化容器
PartFormatterBuilder partFormatterBuilder = new PartFormatterBuilder();//實例化一個局部元素格式化器
partFormatterBuilder.AddFormatter("Title", "跨越IT學員");//將模板表格中Title的值設置為跨越IT學員
formatterContainers.AppendFormatterBuilder(partFormatterBuilder);//添加到工作薄格式容器中,注意只有添加進去了才會生效
CellFormatterBuilder cellFormatterBuilder = new CellFormatterBuilder();//實例化一個單元格格式化器
cellFormatterBuilder.AddFormatter("rptdate", DateTime.Today.ToString("yyyy-MM-dd HH:mm"));//將模板表格中rptdate的值設置為當前日期
formatterContainers.AppendFormatterBuilder(cellFormatterBuilder);//添加到工作薄格式容器中,注意只有添加進去了才會生效
//實例化一個表格格式化器,data是已拆分后的數據源(這里是10條記錄),name表示的模板表格中第一行第一個單元格要填充的數據參數名
TableFormatterBuilder<DataRow> tableFormatterBuilder = new TableFormatterBuilder<DataRow>(data, "name");//這里的數據源設置:data是重點
tableFormatterBuilder.AddFormatters(new Dictionary<string, Func<DataRow, object>>{
{"name",r=>r["Col1"]},//將模板表格中name對應DataTable中的列Col1
{"sex",r=>r["Col2"]},//將模板表格中sex對應DataTable中的列Col2
{"km",r=>r["Col3"]},//將模板表格中km對應DataTable中的列Col3
{"score",r=>r["Col4"]},//將模板表格中score對應DataTable中的列Col4
{"result",r=>r["Col5"]}//將模板表格中result對應DataTable中的列Co5
});
formatterContainers.AppendFormatterBuilder(tableFormatterBuilder);//添加到工作薄格式容器中,注意只有添加進去了才會生效
return formatterContainers;//返回一個模板數據格式化容器
}, 10);//注意這里的10表示動態生成新的工作薄,且每個工作薄顯示10條記錄
Assert.IsTrue(File.Exists(excelPath));
}
測試結果如下:
1.生成的臨時模板及模板配置文件:

2.導出的EXCE結果如下:

3.臨時模板及模板配置文件已被清除。
新增方法二:增加由DataTable導出多Sheet Excel方法(准確的說是修改ToExcel方法,增加一個sheetSize參數),方法定義如下:
/// <summary>
/// 由DataTable導出Excel
/// </summary>
/// <param name="sourceTable">要導出數據的DataTable</param>
/// <param name="sheetName">工作薄名稱,可選</param>
/// <param name="filePath">導出路徑,可選</param>
/// <param name="colNames">需要導出的列名,可選</param>
/// <param name="colAliasNames">導出的列名重命名,可選</param>
/// <param name="colDataFormats">列格式化集合,可選</param>
/// <param name="sheetSize">指定每個工作薄顯示的記錄數,可選(不指定或指定小於0,則表示只生成一個工作薄)</param>
/// <returns></returns>
public static string ToExcel(DataTable sourceTable, string sheetName = "result", string filePath = null, string[] colNames = null, IDictionary<string, string> colAliasNames = null, IDictionary<string, string> colDataFormats = null, int sheetSize = 0)
{
if (sourceTable.Rows.Count <= 0) return null;
if (string.IsNullOrEmpty(filePath))
{
filePath = Common.GetSaveFilePath();
}
if (string.IsNullOrEmpty(filePath)) return null;
bool isCompatible = Common.GetIsCompatible(filePath);
IWorkbook workbook = Common.CreateWorkbook(isCompatible);
ICellStyle headerCellStyle = Common.GetCellStyle(workbook, true);
//ICellStyle cellStyle = Common.GetCellStyle(workbook);
if (colNames == null || colNames.Length <= 0)
{
colNames = sourceTable.Columns.Cast<DataColumn>().OrderBy(c => c.Ordinal).Select(c => c.ColumnName).ToArray();
}
IEnumerable<DataRow> batchDataRows, dataRows = sourceTable.Rows.Cast<DataRow>();
int sheetCount = 0;
if (sheetSize <= 0)
{
sheetSize = sourceTable.Rows.Count;
}
while ((batchDataRows = dataRows.Take(sheetSize)).Count() > 0)
{
Dictionary<int, ICellStyle> colStyles = new Dictionary<int, ICellStyle>();
ISheet sheet = workbook.CreateSheet(sheetName + (++sheetCount).ToString());
IRow headerRow = sheet.CreateRow(0);
// handling header.
for (int i = 0; i < colNames.Length; i++)
{
ICell headerCell = headerRow.CreateCell(i);
if (colAliasNames != null && colAliasNames.ContainsKey(colNames[i]))
{
headerCell.SetCellValue(colAliasNames[colNames[i]]);
}
else
{
headerCell.SetCellValue(colNames[i]);
}
headerCell.CellStyle = headerCellStyle;
sheet.AutoSizeColumn(headerCell.ColumnIndex);
if (colDataFormats != null && colDataFormats.ContainsKey(colNames[i]))
{
colStyles[headerCell.ColumnIndex] = Common.GetCellStyleWithDataFormat(workbook, colDataFormats[colNames[i]]);
}
else
{
colStyles[headerCell.ColumnIndex] = Common.GetCellStyle(workbook);
}
}
// handling value.
int rowIndex = 1;
foreach (DataRow row in batchDataRows)
{
IRow dataRow = sheet.CreateRow(rowIndex);
for (int i = 0; i < colNames.Length; i++)
{
ICell cell = dataRow.CreateCell(i);
//cell.SetCellValue((row[colNames[i]] ?? "").ToString());
//cell.CellStyle = cellStyle;
Common.SetCellValue(cell, (row[colNames[i]] ?? "").ToString(), sourceTable.Columns[colNames[i]].DataType, colStyles);
Common.ReSizeColumnWidth(sheet, cell);
}
rowIndex++;
}
sheet.ForceFormulaRecalculation = true;
dataRows = dataRows.Skip(sheetSize);
}
FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
workbook.Write(fs);
fs.Dispose();
workbook = null;
return filePath;
}
修改代碼部份說明:增加依據指定的每個工作薄顯示的記錄數(sheetSize)來循環拆分數據源及創建多個工作薄;
測試示例代碼如下:
/// <summary>
/// 測試方法:測試將DataTable導出到多工作薄EXCEL
/// </summary>
[TestMethod]
public void TestExportToExcelByDataTable8()
{
DataTable dt = GetDataTable();
string excelPath = ExcelUtility.Export.ToExcel(dt, "sheet", sheetSize: 10);//指定每個工作薄顯示的記錄數
Assert.IsTrue(File.Exists(excelPath));
}
導出的EXCE結果如下:

源代碼同步更新至開源社區的GIT目錄中,具體地址請看我該系列之前的文章有列出,在此就不再說明。
分享我基於NPOI+ExcelReport實現的導入與導出EXCEL類庫:ExcelUtility 其它相關文章鏈接:
分享我基於NPOI+ExcelReport實現的導入與導出EXCEL類庫:ExcelUtility (續2篇-模板導出綜合示例)
