企業應用中常遇到批量上傳資料的需求:客戶使用 Excel 表格錄入大量資料,透過網頁上傳 Excel 檔案,系統自動讀取並批量導入資料庫。核心流程包含三步:讀取 Excel 內容 → 必要欄位驗證 → 導入驗證通過的資料、回報失敗行號與錯誤訊息。
讀取 Excel 的方案眾多,常見有 OLEDB 與第三方元件。考慮到生產環境伺服器不一定安裝 Office 或 OLEDB 提供者,為追求最大相容性,筆者選擇開源元件 Koogra(.NET 實作,SourceForge 專案:https://sourceforge.net/projects/koogra/)。
專案需參考 Ionic.Utils.Zip.dll 與 Net.SourceForge.Koogra.dll。
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using Net.SourceForge.Koogra.Excel;
namespace Gren.Framework.Office
{
///
/// Excel 工具類,封裝 Koogra 實現多樣化讀取
///
public class ExcelUtils
{
private Workbook book;
public ExcelUtils(string path)
{
this.book = new Workbook(path);
}
public ExcelUtils(System.IO.Stream stream)
{
this.book = new Workbook(stream);
}
///
/// 將工作表轉換為 DataTable
///
protected DataTable SaveAsDataTable(Worksheet sheet)
{
DataTable dt = new DataTable();
uint minRow = sheet.Rows.MinRow;
uint maxRow = sheet.Rows.MaxRow;
Row firstRow = sheet.Rows[minRow];
uint minCol = firstRow.Cells.MinCol;
uint maxCol = firstRow.Cells.MaxCol;
// 以第一行作為欄位名
for (uint i = minCol; i <= maxCol; i++)
dt.Columns.Add(firstRow.Cells[i].FormattedValue());
// 從第二行開始讀取資料
for (uint i = minRow + 1; i <= maxRow; i++)
{
Row row = sheet.Rows[i];
if (row != null)
{
DataRow dr = dt.NewRow();
for (uint j = minCol; j <= maxCol; j++)
{
Cell cell = row.Cells[j];
if (cell != null)
dr[Convert.ToInt32(j)] = cell.Value != null ? cell.Value.ToString() : string.Empty;
}
dt.Rows.Add(dr);
}
}
return dt;
}
public DataTable ToDataTable(int index)
{
Worksheet sheet = this.book.Sheets[index];
if (sheet == null)
throw new ApplicationException(string.Format("索引[{0}]所指定的工作表不存在!", index));
return this.SaveAsDataTable(sheet);
}
public DataTable ToDataTable(string sheetName)
{
Worksheet sheet = this.book.Sheets.GetByName(sheetName);
if (sheet == null)
throw new ApplicationException(string.Format("名稱[{0}]所指定的工作表不存在!", sheetName));
return this.SaveAsDataTable(sheet);
}
#region 靜態便捷方法
///
/// Excel 日期儲存格(double)轉 DateTime,失敗嘗試字串解析
///
public static DateTime ParseDateTime(string cellValue)
{
DateTime date = default(DateTime);
double value = default(double);
if (double.TryParse(cellValue, out value))
date = DateTime.FromOADate(value);
else
DateTime.TryParse(cellValue, out date);
return date;
}
public static DataTable TranslateToTable(string path, string sheetName)
{
ExcelUtils utils = new ExcelUtils(path);
return utils.ToDataTable(sheetName);
}
public static DataTable TranslateToTable(string path, int sheetIndex)
{
ExcelUtils utils = new ExcelUtils(path);
return utils.ToDataTable(sheetIndex);
}
public static DataTable TranslateToTable(string path)
{
ExcelUtils utils = new ExcelUtils(path);
return utils.ToDataTable(0);
}
public static DataTable TranslateToTable(System.IO.Stream stream, string sheetName)
{
ExcelUtils utils = new ExcelUtils(stream);
return utils.ToDataTable(sheetName);
}
public static DataTable TranslateToTable(System.IO.Stream stream, int sheetIndex)
{
ExcelUtils utils = new ExcelUtils(stream);
return utils.ToDataTable(sheetIndex);
}
public static DataTable TranslateToTable(System.IO.Stream stream)
{
ExcelUtils utils = new ExcelUtils(stream);
return utils.ToDataTable(0);
}
#endregion
}
}
Excel 中日期型別儲存為 double(OADate 格式),ParseDateTime 先嘗試 double 轉換,失敗再嘗試字串解析(應對使用者將儲存格設為文字格式直接輸入日期的情況)。
以 fileUpload1 為檔案上傳控件,採用記憶體流處理上傳檔案,效能較佳:
if (fileUpload1.HasFile)
{
DataTable dt = null;
try
{
using (System.IO.MemoryStream stream = new System.IO.MemoryStream(fileUpload1.FileBytes))
{
dt = Gren.Framework.Office.ExcelUtils.TranslateToTable(stream, "sheet1");
}
// 取得 DataTable 後,執行自有驗證與入庫邏輯
}
catch (Exception ex)
{
lblMessage.Text = "處理資料檔案錯誤:
";
lblMessage.Text += "" + Server.HtmlEncode(ex.Message) + "";
}
}
else
{
lblMessage.Text = "請選擇資料檔案
";
}
直接使用記憶體流處理上傳檔案速度較快,歡迎測試反饋,若有優化建議也請不吝指教,共同進步。