c# NPOI 匯出23萬條記錄耗時12秒
阿新 • • 發佈:2019-08-18
先上測試程式碼:
string connectionString = "Server=localhost;Initial Catalog=******;User ID=sa;Password=******;"; List<TestData> datas = null; using (SqlConnection db = new SqlConnection(connectionString)) { datas = db.Query<TestData>("SELECT * FROM TestData").ToList(); } System.Console.WriteLine($"資料來源物件 {typeof(TestData).GetProperties().Length} 個欄位,共 {datas.Count} 條記錄,大小 {BinarySerializeHelper.SerializeToBytes(datas).Length/1000/1000} M"); Task.Run(() => { while (true) { System.Console.WriteLine($"{DateTime.Now} 記憶體 : {GC.GetTotalMemory(false) / 1000 / 1000} M"); Thread.Sleep(1000); } }); Stopwatch sw = new Stopwatch(); sw.Start(); byte[] bytes = ExcelHandlerFactory.CreateHandler(datas).CreateExcelBytes(); sw.Stop(); System.Console.WriteLine($"{DateTime.Now} 資料來源轉Excel檔案位元組陣列耗時 : "+sw.ElapsedMilliseconds / 1000 +" 秒"); string path = @"C:\Users\Administrator\Desktop\1.xlsx"; FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write); fs.Write(bytes); fs.Dispose(); System.Console.ReadKey();
測試結果:
就是這記憶體佔用有點高......
原始碼:
using System.Collections.Generic; namespace Wjire.Excel { /// <summary> /// ExcelHandler工廠 /// </summary> public static class ExcelHandlerFactory { /// <summary> /// 建立ExcelHandler /// </summary> /// <typeparam name="T"></typeparam> /// <param name="sources">資料來源</param> /// <param name="choosedFields">需要匯出的欄位,可不傳,則匯出所有欄位</param> /// <returns></returns> public static ExcelHandler<T> CreateHandler<T>(IEnumerable<T> sources, HashSet<string> choosedFields = null) { return new ExcelHandler<T>(sources, choosedFields); } } }
using NPOI.HSSF.UserModel; using NPOI.SS.UserModel; using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Wjire.Excel { /// <summary> /// 報表匯出處理者 /// </summary> public sealed class ExcelHandler<TSource> { /// <summary> /// 資料來源 /// </summary> private readonly IEnumerable<TSource> _sources; /// <summary> /// 需要匯出的列資訊 /// </summary> private readonly ColumnInfo[] _columnInfos; /// <summary> /// 工作簿 /// </summary> private IWorkbook _workbook; /// <summary> /// 工作頁 /// </summary> private ISheet _sheet; /// <summary> /// 單元格樣式 /// </summary> private ICellStyle _cellStyle; /// <summary> /// 單元格樣式提供器 /// </summary> private ICellStyleProvider _provider; internal ExcelHandler(IEnumerable<TSource> sources, HashSet<string> choosedFields) { _sources = sources; _columnInfos = GetColumnInfosOfExport(choosedFields); } /// <summary> /// 資料來源轉位元組 /// </summary> /// <returns></returns> public byte[] CreateExcelBytes() { using (var ms = CreateExcelStream()) { return ms.ToArray(); } } /// <summary> /// 資料來源轉excel流 /// </summary> /// <returns></returns> public MemoryStream CreateExcelStream() { try { _workbook = new HSSFWorkbook(); _cellStyle = (_provider ?? DefaultCellStyleProvider.Singleton.Value).CreateCellStyle(_workbook); int sheetIndex = 1; CreateSheetWithHeader(sheetIndex); int rowIndex = 1; foreach (TSource entity in _sources) { //03版 excel 一個 _sheet 最多 65535 行 if (rowIndex == 65535) { sheetIndex++; CreateSheetWithHeader(sheetIndex); rowIndex = 1; } CreateDataRow(rowIndex, entity); rowIndex++; } MemoryStream ms = new MemoryStream(); _workbook.Write(ms); return ms; } finally { _workbook?.Close(); } } /// <summary> /// 建立Sheet及列頭 /// </summary> private void CreateSheetWithHeader(int sheetIndex) { _sheet = _workbook.CreateSheet("第 " + sheetIndex + " 頁"); //凍結首行首列 _sheet.CreateFreezePane(0, 1); IRow header = _sheet.CreateRow(0); for (int i = 0; i < _columnInfos.Length; i++) { ICell cell = header.CreateCell(i); cell.SetCellValue(_columnInfos[i].CellDisplayAttribute.Name); cell.CellStyle = _cellStyle; //自適應寬度 _sheet.AutoSizeColumn(i); } } /// <summary> /// 建立資料行 /// </summary> /// <param name="rowIndex">行索引</param> /// <param name="entity">資料</param> private void CreateDataRow(int rowIndex, object entity) { IRow dataRow = _sheet.CreateRow(rowIndex); for (int i = 0; i < _columnInfos.Length; i++) { ICell cell = dataRow.CreateCell(i); cell.CellStyle = _cellStyle; object value = _columnInfos[i].PropertyInfo.GetValue(entity, null); SetCellValue(value, cell); } } /// <summary> /// 設定單元格值 /// </summary> /// <param name="value"></param> /// <param name="cell"></param> private void SetCellValue(object value, ICell cell) { if (value == null) { cell.SetCellValue(string.Empty); return; } Type type = value.GetType(); switch (type.Name) { case "DateTime": case "String": case "Boolean": cell.SetCellValue(value.ToString()); break; case "Byte": case "Int16": case "Int32": case "Int64": case "Single": case "Double": case "Decimal": cell.SetCellValue(Convert.ToDouble(value)); break; default: cell.SetCellValue(string.Empty); break; } } /// <summary> /// 設定excel單元格樣式提供器 /// </summary> /// <param name="provider"></param> /// <returns></returns> public ExcelHandler<TSource> SetCellStyleProvider(ICellStyleProvider provider) { _provider = provider; return this; } /// <summary> /// 獲取需要匯出的列資訊 /// </summary> /// <param name="choosedFields"></param> /// <returns></returns> private ColumnInfo[] GetColumnInfosOfExport(HashSet<string> choosedFields) { ColumnInfo[] columnInfos = ColumnInfoContainer.GetColumnInfo(typeof(TSource)); return choosedFields?.Count > 0 ? columnInfos.Where(w => choosedFields.Contains(w.PropertyInfo.Name)).ToArray() : columnInfos; } } }
using System.Reflection; namespace Wjire.Excel { /// <summary> /// 列資訊 /// </summary> public class ColumnInfo { internal PropertyInfo PropertyInfo { get; set; } internal CellDisplayAttribute CellDisplayAttribute { get; set; } } }View Code
using System; namespace Wjire.Excel { /// <summary> /// excel 單元格資料顯示自定義特性類 /// </summary> [AttributeUsage(AttributeTargets.Property)] public sealed class CellDisplayAttribute : Attribute { /// <summary> /// 自定義列名 /// </summary> public string Name { get; set; } /// <summary> /// 建構函式 /// </summary> /// <param name="name">自定義列名</param> public CellDisplayAttribute(string name) { Name = name; } } }View Code
using NPOI.SS.UserModel; namespace Wjire.Excel { /// <summary> /// 單元格樣式提供器介面 /// </summary> public interface ICellStyleProvider { ICellStyle CreateCellStyle(IWorkbook workbook); } }View Code
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Wjire.Excel { /// <summary> /// 資料來源列資訊容器 /// </summary> internal static class ColumnInfoContainer { private static readonly Dictionary<Type, ColumnInfo[]> Container = new Dictionary<Type, ColumnInfo[]>(); /// <summary> /// 獲取資料來源列資訊 /// </summary> /// <param name="sourceType">資料來源類型別</param> /// <returns></returns> internal static ColumnInfo[] GetColumnInfo(Type sourceType) { if (Container.TryGetValue(sourceType, out ColumnInfo[] infos)) { return infos; } infos = sourceType .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(propertyInfo => propertyInfo.GetCustomAttribute<CellDisplayAttribute>(true) != null) .Select(propertyInfo => new ColumnInfo { PropertyInfo = propertyInfo, CellDisplayAttribute = propertyInfo.GetCustomAttribute<CellDisplayAttribute>() }).ToArray(); Container.Add(sourceType, infos); return infos; } } }View Code
using NPOI.SS.UserModel; using System; namespace Wjire.Excel { /// <summary> /// 預設單元格樣式提供器 /// </summary> internal class DefaultCellStyleProvider : ICellStyleProvider { internal static Lazy<DefaultCellStyleProvider> Singleton = new Lazy<DefaultCellStyleProvider>(() => new DefaultCellStyleProvider()); private DefaultCellStyleProvider() { } /// <summary> /// 建立單元格樣式 /// </summary> /// <param name="workbook"></param> /// <returns></returns> public ICellStyle CreateCellStyle(IWorkbook workbook) { ICellStyle cellStyle = workbook.CreateCellStyle(); cellStyle.Alignment = HorizontalAlignment.Center; //cellStyle.VerticalAlignment = VerticalAlignment.Center;//垂直居中非常影響效率,不建議開啟該功能 IFont font = workbook.CreateFont(); font.FontHeightInPoints = 11; //font.Boldweight = 700; cellStyle.SetFont(font); //邊框 //cellStyle.BorderBottom = BorderStyle.Thin; //cellStyle.BorderLeft = BorderStyle.Thin; //cellStyle.BorderRight = BorderStyle.Thin; //cellStyle.BorderTop = BorderStyle.Thin; return cellStyle; } } }View Code
幾點說明:
1.NPOI 用的最新版本:2.4.1;
2.程式碼中用的 HSSFWorkbook ,不僅僅是為了相容 word2003,在測試的時候發現,如果用 XSSFWorkbook ,耗時慢了N個數量級,不知道是不是哪裡姿勢不對;
3.單元格的寬度只在標題欄設定了,所以匯出來的Excel可能比較醜.原因是:
1)如果根據單元格內容的長度來調整的話,由於每一個單元格內容的長度都肯能不一樣,太耗時,沒必要,不如滑鼠點兩下來得快;
2)雖然NPOI有個功能可以在一個sheet的資料填充完後,設定單元格的寬度自適應,但是測試了下,太太太太慢了.估計是在遍歷所有的單元格,一個一個計算;
3)還有一個折中的辦法,就是根據第一行資料的各個單元格內容來調整寬度,因為有些時候,資料物件每個屬性的值的長度都不會差太多.但是當長度不一的時候,會讓人誤以為那些長的單元格的內容已經顯示完了,所以也捨棄了這個功能.
4.測試的時候發現,如果把某一列的單元格設定成超連結,點選可以開啟瀏覽器檢視那種,非常非常非常非常慢.慘不忍睹.不知道是不是姿勢不對,所以也捨棄了該功