使用NPOI匯入匯出標準Excel(原始碼)
嘗試過很多Excel匯入匯出方法,都不太理想,無意中逛到oschina時,發現了NPOI,無需Office COM元件且不依賴Office,頓時驚為天人,懷著無比激動的心情寫下此文。
曾使用過的方法
- 直接匯出html,修改後綴名為.xls,這個方法有點像騙人的把戲,而且不能再匯入
- 使用Jet OLEDB引擎來進行匯入匯出,完全使用sql語句來進行操作,缺點能控制的東西非常有限,比如格式就難以控制
- 使用Office COM元件進行匯入匯出,對環境依賴性太強(如“檢索 COM 類工廠…”錯誤);且需要通過開啟Excel.exe程序進行操作;雖然可以通過關閉工作表以及Marshal.ReleaseComObject
關於NPOI
NPOI是POI專案的.NET版本,是由@Tony Qu(http://tonyqus.cnblogs.com/)等大俠基於POI開發的,可以從http://npoi.codeplex.com/下載到它的最新版本。它不使用Office COM元件(Microsoft.Office.Interop.XXX.dll),不需要安裝Microsoft Office,支援對Office 97-2003的檔案格式,功能比較強大。更詳細的說明請看作者的部落格或官方網站。
它的以下一些特性讓我相當喜歡:
- 支援對標準的Excel讀寫
- 支援對流(Stream)的讀寫 (而Jet OLEDB和Office COM都只能針對檔案)
- 支援大部分Office COM元件的常用功能
- 效能優異 (相對於前面的方法)
- 使用簡單,易上手
使用NPOI
本文使用的是它當前的最新版本1.2.4,此版本的程式集縮減至2個:NPOI.dll、Ionic.Zip.dll,直接引用到專案中即可。
對於我們開發者使用的物件主要位於NPOI.HSSF.UserModel空間下,主要有HSSFWorkbook、HSSFSheet、HSSFRow、HSSFCell,對應的介面為位於NPOI.SS.UserModel空間下的IWorkbook、ISheet、IRow、ICell,分別對應Excel檔案、工作表、行、列。
簡單演示一下建立一個Workbook物件,新增一個工作表,在工作表中新增一行一列:
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
public class NPOIWrite
{
void CreateSheet()
{
IWorkbook workbook = new HSSFWorkbook();//建立Workbook物件
ISheet sheet = workbook.CreateSheet("Sheet1");//建立工作表
IRow row = sheet.CreateRow(0);//在工作表中新增一行
ICell cell = row.CreateCell(0);//在行中新增一列
cell.SetCellValue("test");//設定列的內容
}
}
相應的讀取程式碼:
using System.IO;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
public class NPOIRead
{
void GetSheet(Stream stream)
{
IWorkbook workbook = new HSSFWorkbook(stream);//從流內容建立Workbook物件
ISheet sheet = workbook.GetSheetAt(0);//獲取第一個工作表
IRow row = sheet.GetRow(0);//獲取工作表第一行
ICell cell = row.GetCell(0);//獲取行的第一列
string value = cell.ToString();//獲取列的值
}
}
使用NPOI匯出
從DataTable讀取內容來建立Workbook物件:
public static MemoryStream RenderToExcel(DataTable table)
{
MemoryStream ms = new MemoryStream();
using (table)
{
using (IWorkbook workbook = new HSSFWorkbook())
{
using (ISheet sheet = workbook.CreateSheet())
{
IRow headerRow = sheet.CreateRow(0);
// handling header.
foreach (DataColumn column in table.Columns)
headerRow.CreateCell(column.Ordinal).SetCellValue(column.Caption);//If Caption not set, returns the ColumnName value
// handling value.
int rowIndex = 1;
foreach (DataRow row in table.Rows)
{
IRow dataRow = sheet.CreateRow(rowIndex);
foreach (DataColumn column in table.Columns)
{
dataRow.CreateCell(column.Ordinal).SetCellValue(row[column].ToString());
}
rowIndex++;
}
workbook.Write(ms);
ms.Flush();
ms.Position = 0;
}
}
}
return ms;
}
如果看不慣DataTable,那麼DataReader也行:
public static MemoryStream RenderToExcel(IDataReader reader)
{
MemoryStream ms = new MemoryStream();
using (reader)
{
using (IWorkbook workbook = new HSSFWorkbook())
{
using (ISheet sheet = workbook.CreateSheet())
{
IRow headerRow = sheet.CreateRow(0);
int cellCount = reader.FieldCount;
// handling header.
for (int i = 0; i < cellCount; i++)
{
headerRow.CreateCell(i).SetCellValue(reader.GetName(i));
}
// handling value.
int rowIndex = 1;
while (reader.Read())
{
IRow dataRow = sheet.CreateRow(rowIndex);
for (int i = 0; i < cellCount; i++)
{
dataRow.CreateCell(i).SetCellValue(reader[i].ToString());
}
rowIndex++;
}
workbook.Write(ms);
ms.Flush();
ms.Position = 0;
}
}
}
return ms;
}
以上程式碼把建立的Workbook物件儲存到流中,可以通過以下方法輸出到瀏覽器,或是儲存到硬碟中:
static void SaveToFile(MemoryStream ms, string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
byte[] data = ms.ToArray();
fs.Write(data, 0, data.Length);
fs.Flush();
data = null;
}
}
static void RenderToBrowser(MemoryStream ms, HttpContext context, string fileName)
{
if (context.Request.Browser.Browser == "IE")
fileName = HttpUtility.UrlEncode(fileName);
context.Response.AddHeader("Content-Disposition", "attachment;fileName=" + fileName);
context.Response.BinaryWrite(ms.ToArray());
}
使用NPOI匯入
需要注意的是,sheet.LastRowNum = sheet.PhysicalNumberOfRows - 1,這裡可能存在BUG:當沒有資料或只有一行資料時sheet.LastRowNum為0,PhysicalNumberOfRows 表現正常。
這裡讀取流中的Excel來建立Workbook物件,並轉換成DataTable:
static DataTable RenderFromExcel(Stream excelFileStream)
{
using (excelFileStream)
{
using (IWorkbook workbook = new HSSFWorkbook(excelFileStream))
{
using (ISheet sheet = workbook.GetSheetAt(0))//取第一個表
{
DataTable table = new DataTable();
IRow headerRow = sheet.GetRow(0);//第一行為標題行
int cellCount = headerRow.LastCellNum;//LastCellNum = PhysicalNumberOfCells
int rowCount = sheet.LastRowNum;//LastRowNum = PhysicalNumberOfRows - 1
//handling header.
for (int i = headerRow.FirstCellNum; i < cellCount; i++)
{
DataColumn column = new DataColumn(headerRow.GetCell(i).StringCellValue);
table.Columns.Add(column);
}
for (int i = (sheet.FirstRowNum + 1); i <= rowCount; i++)
{
IRow row = sheet.GetRow(i);
DataRow dataRow = table.NewRow();
if (row != null)
{
for (int j = row.FirstCellNum; j < cellCount; j++)
{
if (row.GetCell(j) != null)
dataRow[j] = GetCellValue(row.GetCell(j));
}
}
table.Rows.Add(dataRow);
}
return table;
}
}
}
}
或者是直接生成SQL語句來插入到資料庫:
public static int RenderToDb(Stream excelFileStream, string insertSql, DBAction dbAction)
{
int rowAffected = 0;
using (excelFileStream)
{
using (IWorkbook workbook = new HSSFWorkbook(excelFileStream))
{
using (ISheet sheet = workbook.GetSheetAt(0))//取第一個工作表
{
StringBuilder builder = new StringBuilder();
IRow headerRow = sheet.GetRow(0);//第一行為標題行
int cellCount = headerRow.LastCellNum;//LastCellNum = PhysicalNumberOfCells
int rowCount = sheet.LastRowNum;//LastRowNum = PhysicalNumberOfRows - 1
for (int i = (sheet.FirstRowNum + 1); i <= rowCount; i++)
{
IRow row = sheet.GetRow(i);
if (row != null)
{
builder.Append(insertSql);
builder.Append(" values (");
for (int j = row.FirstCellNum; j < cellCount; j++)
{
builder.AppendFormat("'{0}',", GetCellValue(row.GetCell(j)).Replace("'", "''"));
}
builder.Length = builder.Length - 1;
builder.Append(");");
}
if ((i % 50 == 0 || i == rowCount) && builder.Length > 0)
{
//每50條記錄一次批量插入到資料庫
rowAffected += dbAction(builder.ToString());
builder.Length = 0;
}
}
}
}
}
return rowAffected;
}
這裡的Excel可能沒有資料,所以可以加一個方法來檢測:
public static bool HasData(Stream excelFileStream)
{
using (excelFileStream)
{
using (IWorkbook workbook = new HSSFWorkbook(excelFileStream))
{
if (workbook.NumberOfSheets > 0)
{
using (ISheet sheet = workbook.GetSheetAt(0))
{
return sheet.PhysicalNumberOfRows > 0;
}
}
}
}
return false;
}
結尾
好吧,不說啥了,放程式碼:點選下載
前些日子做了一個簡單的winform程式,需要匯出的功能,剛開始省事直接使用微軟的元件,但是匯出之後發現效率極其低下,絕對像web那樣使用npoi元件,因此簡單的進行了整理,包括直接根據DataTable匯出excel及DataGridview匯出excel,版本是1.2.4,下面貼下主要程式碼兩種方式,1、NPOI匯出excel、 2、普通的匯出excel
下面貼下主要程式碼:NPOI匯出
/// <summary>
/// DataTable匯出到Excel檔案
/// </summary>
/// <param name="dtSource">源DataTable</param>
/// <param name="strHeaderText">表頭文字</param>
/// <param name="strFileName">儲存位置</param>
public static void DataTableToExcel(DataTable dtSource, string strHeaderText, string strFileName)
{
using (MemoryStream ms = DataTableToExcel(dtSource, strHeaderText))
{
using (FileStream fs = new FileStream(strFileName, FileMode.Create, FileAccess.Write))
{
byte[] data = ms.ToArray();
fs.Write(data, 0, data.Length);
fs.Flush();
}
}
}
/// <summary>
/// DataTable匯出到Excel的MemoryStream
/// </summary>
/// <param name="dtSource">源DataTable</param>
/// <param name="strHeaderText">表頭文字</param>
public static MemoryStream DataTableToExcel(DataTable dtSource, string strHeaderText)
{
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = (HSSFSheet)workbook.CreateSheet();
#region 右擊檔案 屬性資訊
{
DocumentSummaryInformation dsi = PropertySetFactory.CreateDocumentSummaryInformation();
dsi.Company = "NPOI";
workbook.DocumentSummaryInformation = dsi;
SummaryInformation si = PropertySetFactory.CreateSummaryInformation();
si.Author = "檔案作者資訊"; //填加xls檔案作者資訊
si.ApplicationName = "建立程式資訊"; //填加xls檔案建立程式資訊
si.LastAuthor = "最後儲存者資訊"; //填加xls檔案最後儲存者資訊
si.Comments = "作者資訊"; //填加xls檔案作者資訊
si.Title = "標題資訊"; //填加xls檔案標題資訊
si.Subject = "主題資訊";//填加檔案主題資訊
si.CreateDateTime = System.DateTime.Now;
workbook.SummaryInformation = si;
}
#endregion
HSSFCellStyle dateStyle = (HSSFCellStyle)workbook.CreateCellStyle();
HSSFDataFormat format = (HSSFDataFormat)workbook.CreateDataFormat();
dateStyle.DataFormat = format.GetFormat("yyyy-mm-dd");
//取得列寬
int[] arrColWidth = new int[dtSource.Columns.Count];
foreach (DataColumn item in dtSource.Columns)
{
arrColWidth[item.Ordinal] = Encoding.GetEncoding(936).GetBytes(item.ColumnName.ToString()).Length;
}
for (int i = 0; i < dtSource.Rows.Count; i++)
{
for (int j = 0; j < dtSource.Columns.Count; j++)
{
int intTemp = Encoding.GetEncoding(936).GetBytes(dtSource.Rows[i][j].ToString()).Length;
if (intTemp > arrColWidth[j])
{
arrColWidth[j] = intTemp;
}
}
}
int rowIndex = 0;
foreach (DataRow row in dtSource.Rows)
{
#region 新建表,填充表頭,填充列頭,樣式
if (rowIndex == 65535 || rowIndex == 0)
{
if (rowIndex != 0)
{
sheet = (HSSFSheet)workbook.CreateSheet();
}
#region 表頭及樣式
{
HSSFRow headerRow = (HSSFRow)sheet.CreateRow(0);
headerRow.HeightInPoints = 25;
headerRow.CreateCell(0).SetCellValue(strHeaderText);
HSSFCellStyle headStyle = (HSSFCellStyle)workbook.CreateCellStyle();
// headStyle.Alignment = CellHorizontalAlignment.CENTER;
HSSFFont font = (HSSFFont)workbook.CreateFont();
font.FontHeightInPoints = 20;
font.Boldweight = 700;
headStyle.SetFont(font);
headerRow.GetCell(0).CellStyle = headStyle;
// sheet.AddMergedRegion(new Region(0, 0, 0, dtSource.Columns.Count - 1));
//headerRow.Dispose();
}
#endregion
#region 列頭及樣式
{
HSSFRow headerRow = (HSSFRow)sheet.CreateRow(1);
HSSFCellStyle headStyle = (HSSFCellStyle)workbook.CreateCellStyle();
//headStyle.Alignment = CellHorizontalAlignment.CENTER;
HSSFFont font = (HSSFFont)workbook.CreateFont();
font.FontHeightInPoints = 10;
font.Boldweight = 700;
headStyle.SetFont(font);
foreach (DataColumn column in dtSource.Columns)
{
headerRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);
headerRow.GetCell(column.Ordinal).CellStyle = headStyle;
//設定列寬
sheet.SetColumnWidth(column.Ordinal, (arrColWidth[column.Ordinal] + 1) * 256);
}
// headerRow.Dispose();
}
#endregion
rowIndex = 2;
}
#endregion
#region 填充內容
HSSFRow dataRow = (HSSFRow)sheet.CreateRow(rowIndex);
foreach (DataColumn column in dtSource.Columns)
{
HSSFCell newCell =(HSSFCell) dataRow.CreateCell(column.Ordinal);
string drValue = row[column].ToString();
switch (column.DataType.ToString())
{
case "System.String"://字串型別
newCell.SetCellValue(drValue);
break;
case "System.DateTime"://日期型別
System.DateTime dateV;
System.DateTime.TryParse(drValue, out dateV);
newCell.SetCellValue(dateV);
newCell.CellStyle = dateStyle;//格式化顯示
break;
case "System.Boolean"://布林型
bool boolV = false;
bool.TryParse(drValue, out boolV);
newCell.SetCellValue(boolV);
break;
case "System.Int16"://整型
case "System.Int32":
case "System.Int64":
case "System.Byte":
int intV = 0;
int.TryParse(drValue, out intV);
newCell.SetCellValue(intV);
break;
case "System.Decimal"://浮點型
case "System.Double":
double doubV = 0;
double.TryParse(drValue, out doubV);
newCell.SetCellValue(doubV);
break;
case "System.DBNull"://空值處理
newCell.SetCellValue("");
break;
default:
newCell.SetCellValue("");
break;
}
}
#endregion
rowIndex++;
}
using (MemoryStream ms = new MemoryStream())
{
workbook.Write(ms);
ms.Flush();
ms.Position = 0;
sheet.Dispose();
//workbook.Dispose();//一般只用寫這一個就OK了,他會遍歷並釋放所有資源,但當前版本有問題所以只釋放sheet
return ms;
}
}
普通excel匯出
#region 匯出excel
public static void ExportExcel(string fileName, DataGridView myDGV,bool isShowDialog)
{
string saveFileName = "";
if (isShowDialog)
{
//bool fileSaved = false;
SaveFileDialog saveDialog = new SaveFileDialog();
saveDialog.DefaultExt = "xls";
saveDialog.Filter = "Excel檔案|*.xls";
saveDialog.FileName = fileName;
saveDialog.ShowDialog();
saveFileName = saveDialog.FileName;
if (saveFileName.IndexOf(":") < 0) return; //被點了取消
}
else
{
// saveFileName = Application.StartupPath + @"\匯出記錄\" + fileName + ".xls";
saveFileName = fileName;
}
Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
if (xlApp == null)
{
MessageBox.Show("無法建立Excel物件,可能您的機子未安裝Excel");
return;
}
Microsoft.Office.Interop.Excel.Workbooks workbooks = xlApp.Workbooks;
Microsoft.Office.Interop.Excel.Workbook workbook = workbooks.Add(Microsoft.Office.Interop.Excel.XlWBATemplate.xlWBATWorksheet);
Microsoft.Office.Interop.Excel.Worksheet worksheet = (Microsoft.Office.Interop.Excel.Worksheet)workbook.Worksheets[1];//取得sheet1
//寫入標題
for (int i = 0; i < myDGV.ColumnCount; i++)
{
worksheet.Cells[1, i + 1] = myDGV.Columns[i].HeaderText;
}
//寫入數值
for (int r = 0; r < myDGV.Rows.Count; r++)
{
for (int i = 0; i < myDGV.ColumnCount; i++)
{
if (myDGV[i, r].ValueType == typeof(string)
|| myDGV[i, r].ValueType == typeof(DateTime))//這裡就是驗證DataGridView單元格中的型別,如果是string或是DataTime型別,則在放入緩 存時在該內容前加入" ";
{
worksheet.Cells[r + 2, i + 1] = "'" + myDGV.Rows[r].Cells[i].Value;
}
else
{
worksheet.Cells[r + 2, i + 1] = myDGV.Rows[r].Cells[i].Value;
}
}
System.Windows.Forms.Application.DoEvents();
}
worksheet.Columns.EntireColumn.AutoFit();//列寬自適應
//if (Microsoft.Office.Interop.cmbxType.Text != "Notification")
//{
// Excel.Range rg = worksheet.get_Range(worksheet.Cells[2, 2], worksheet.Cells[ds.Tables[0].Rows.Count + 1, 2]);
// rg.NumberFormat = "00000000";
//}
if (saveFileName != "")
{
try
{
workbook.Saved = true;
workbook.SaveCopyAs(saveFileName);
//fileSaved = true;
}
catch (Exception ex)
{
//fileSaved = false;
MessageBox.Show("匯出檔案時出錯,檔案可能正被開啟!\n" + ex.Message);
}
}
//else
//{
// fileSaved = false;
//}
xlApp.Quit();
GC.Collect();//強行銷燬
// if (fileSaved && System.IO.File.Exists(saveFileName)) System.Diagnostics.Process.Start(saveFileName); //開啟EXCEL
MessageBox.Show(fileName + "儲存成功", "提示", MessageBoxButtons.OK);
}
#endregion
5萬條資料效能測試
下面附上原始碼,裡面有NPOI和普通匯出excel的效能比較。
下載原始碼 如果您覺的文章不錯,請點選推薦!