(1)OleDB方式
優點:將Excel直接當作資料來源處理,透過SQL直接讀取內容,讀取速度較快。
缺點:讀取資料方式不夠靈活,無法直接讀取某一個儲存格,只有將整個Sheet頁讀取出來後(結果為Datatable)再在Datatable中根據行列數來取得指定的值。
當Excel資料量很大時。會非常佔用內存,當內存不夠時會拋出內存溢出的異常。
讀取程式碼如下:
public DataTable GetExcelTableByOleDB(string strExcelPath, string tableName) { try { DataTable dtExcel = new DataTable(); //数据表 DataSet ds = new DataSet(); //获取文件扩展名 string strExtension = System.IO.Path.GetExtension(strExcelPath); string strFileName = System.IO.Path.GetFileName(strExcelPath); //Excel的连接 OleDbConnection objConn = null; switch (strExtension) { case ".xls": objConn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + strExcelPath + ";" + "Extended Properties=\"Excel 8.0;HDR=NO;IMEX=1;\""); break; case ".xlsx": objConn = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + strExcelPath + ";" + "Extended Properties=\"Excel 12.0;HDR=NO;IMEX=1;\""); break; default: objConn = null; break; } if (objConn == null) { return null; } objConn.Open(); //获取Excel中所有Sheet表的信息 //System.Data.DataTable schemaTable = objConn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, null); //获取Excel的第一个Sheet表名 //string tableName = schemaTable.Rows[0][2].ToString().Trim(); string strSql = "select * from [" + tableName + "]"; //获取Excel指定Sheet表中的信息 OleDbCommand objCmd = new OleDbCommand(strSql, objConn); OleDbDataAdapter myData = new OleDbDataAdapter(strSql, objConn); myData.Fill(ds, tableName);//填充数据 objConn.Close(); //dtExcel即为excel文件中指定表中存储的信息 dtExcel = ds.Tables[tableName]; return dtExcel; } catch { return null; } }
下面說明一下連接字串
HDR=Yes,這代表第一行是標題,不做資料使用(但是我在實際使用中,如果第一行行存在複雜數值,那麼讀取得到的Datatable列標題會自動設定為F1、F2等方式命名,與實際應用不符,所以當時是透過HDR=No方式將所有內容讀取到Datatable中,然後手動將第一行設定成標題的);IMEX ( IMport EXport mode )設定
IMEX 有三種模式:
0 is Export mode
1 is Import mode
2 is Linked mode (full update capabilities) 我這裡要特別說明
參數了,因為不同的模式代表不同的讀寫行為:
當IMEX=0 時為“匯出模式”,這個模式開啟的Excel 檔案只能用來做“寫入”用途。
當 IMEX=1 時為“匯入模式”,這個模式開啟的 Excel 檔案只能用來做“讀取”用途。
當 IMEX=2 時為“連結模式”,此模式開啟的 Excel 檔案可同時支援“讀取”與“寫入”用途。
---------------------------------
另外,讀取Excel2007版本的檔案時,版本應該從8.0改為12.0,同時驅動不能再用Jet,而應該用ACE。負責會造成「找不到可安裝的 ISAM」的錯誤。
---------------------------------
在網路上也發現採用這種方式存在取出的Sheet表的數量多於實際Excel表中的Sheet表個數的情況,其原因有二:
1. 取出的名稱中,包括了XL命名管理器中的名稱(參見XL2007的公式--命名管理器,快速鍵Crtl+F3);
2. 取出的名稱中,包含了FilterDatabase後綴的, 這是XL用來記錄Filter範圍的。
對於第一點比較簡單, 刪除已有命名管理器中的內容即可;第二點處理起來比較麻煩, Filter刪除後這些名稱依然保留著,簡單的做法是新增Sheet然後將原Sheet Copy進去。但實際情況並不能為每個Excel做以上檢查。下面給出了過濾的方案。 (此問題我們有驗證過,大家自己驗證一下吧)
//objConn为读取Excel的链接,下面通过过滤来获取有效的Sheet页名称集合 System.Data.DataTable schemaTable = objConn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, null); List<string> lstSheetNames = new List<string>(); for (int i = 0; i < schemaTable.Rows.Count; i++) { string strSheetName = (string)dtSheetName.Rows[i]["TABLE_NAME"]; if (strSheetName.Contains("$") && !strSheetName.Replace("'", "").EndsWith("$")) { //过滤无效SheetName完毕.... continue; } if (lstSheetNames != null && !lstSheetNames.Contains(strSheetName)) lstSheetNames.Add(strSheetName); }
因為讀取出來無效SheetName一般情況都不會是$。如果SheetName有一些特殊符號,讀取出來的SheetName會自動加上單引號。例如在Excel中將SheetName編輯成MySheet(1),此時讀取出來的SheetName就為:'MySheet(1)$',所以判斷最後一個字元是不是$之前最好過濾一下單引號。
---------------------------------
(2)Com組件的方式(透過加入Microsoft.Office .Interop.Excel引用實作)
優點:能夠非常靈活的讀取Excel中的數據,使用者可以靈活的呼叫各種函數進行處理。
缺點:基於單元格的處理,讀取速度較慢,對於資料量較大的檔案最好不要使用此種方式讀取。
需要新增對應的DLL引用,必須存在此引用才可使用,如果是Web網站部署在IIS上時,還需要伺服器機子已安裝了Excel,有時候還需要設定IIS權限。
讀取代碼如下:
private Stopwatch wath = new Stopwatch(); /// <summary> /// 使用COM读取Excel /// </summary> /// <param name="excelFilePath">路径</param> /// <returns>DataTabel</returns> public System.Data.DataTable GetExcelData(string excelFilePath) { Excel.Application app = new Excel.Application(); Excel.Sheets sheets; Excel.Workbook workbook = null; object oMissiong = System.Reflection.Missing.Value; System.Data.DataTable dt = new System.Data.DataTable(); wath.Start(); try { if (app == null) { return null; } workbook = app.Workbooks.Open(excelFilePath, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong); //将数据读入到DataTable中——Start sheets = workbook.Worksheets; Excel.Worksheet worksheet = (Excel.Worksheet)sheets.get_Item(1);//读取第一张表 if (worksheet == null) return null; string cellContent; int iRowCount = worksheet.UsedRange.Rows.Count; int iColCount = worksheet.UsedRange.Columns.Count; Excel.Range range; //负责列头Start DataColumn dc; int ColumnID = 1; range = (Excel.Range)worksheet.Cells[1, 1]; while (range.Text.ToString().Trim() != "") { dc = new DataColumn(); dc.DataType = System.Type.GetType("System.String"); dc.ColumnName = range.Text.ToString().Trim(); dt.Columns.Add(dc); range = (Excel.Range)worksheet.Cells[1, ++ColumnID]; } //End for (int iRow = 2; iRow <= iRowCount; iRow++) { DataRow dr = dt.NewRow(); for (int iCol = 1; iCol <= iColCount; iCol++) { range = (Excel.Range)worksheet.Cells[iRow, iCol]; cellContent = (range.Value2 == null) ? "" : range.Text.ToString(); dr[iCol - 1] = cellContent; } dt.Rows.Add(dr); } wath.Stop(); TimeSpan ts = wath.Elapsed; //将数据读入到DataTable中——End return dt; } catch { return null; } finally { workbook.Close(false, oMissiong, oMissiong); System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook); workbook = null; app.Workbooks.Close(); app.Quit(); System.Runtime.InteropServices.Marshal.ReleaseComObject(app); app = null; GC.Collect(); GC.WaitForPendingFinalizers(); } } /// <summary> /// 使用COM,多线程读取Excel(1 主线程、4 副线程) /// </summary> /// <param name="excelFilePath">路径</param> /// <returns>DataTabel</returns> public System.Data.DataTable ThreadReadExcel(string excelFilePath) { Excel.Application app = new Excel.Application(); Excel.Sheets sheets = null; Excel.Workbook workbook = null; object oMissiong = System.Reflection.Missing.Value; System.Data.DataTable dt = new System.Data.DataTable(); wath.Start(); try { if (app == null) { return null; } workbook = app.Workbooks.Open(excelFilePath, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong, oMissiong); //将数据读入到DataTable中——Start sheets = workbook.Worksheets; Excel.Worksheet worksheet = (Excel.Worksheet)sheets.get_Item(1);//读取第一张表 if (worksheet == null) return null; string cellContent; int iRowCount = worksheet.UsedRange.Rows.Count; int iColCount = worksheet.UsedRange.Columns.Count; Excel.Range range; //负责列头Start DataColumn dc; int ColumnID = 1; range = (Excel.Range)worksheet.Cells[1, 1]; while (iColCount >= ColumnID) { dc = new DataColumn(); dc.DataType = System.Type.GetType("System.String"); string strNewColumnName = range.Text.ToString().Trim(); if (strNewColumnName.Length == 0) strNewColumnName = "_1"; //判断列名是否重复 for (int i = 1; i < ColumnID; i++) { if (dt.Columns[i - 1].ColumnName == strNewColumnName) strNewColumnName = strNewColumnName + "_1"; } dc.ColumnName = strNewColumnName; dt.Columns.Add(dc); range = (Excel.Range)worksheet.Cells[1, ++ColumnID]; } //End //数据大于500条,使用多进程进行读取数据 if (iRowCount - 1 > 500) { //开始多线程读取数据 //新建线程 int b2 = (iRowCount - 1) / 10; DataTable dt1 = new DataTable("dt1"); dt1 = dt.Clone(); SheetOptions sheet1thread = new SheetOptions(worksheet, iColCount, 2, b2 + 1, dt1); Thread othread1 = new Thread(new ThreadStart(sheet1thread.SheetToDataTable)); othread1.Start(); //阻塞 1 毫秒,保证第一个读取 dt1 Thread.Sleep(1); DataTable dt2 = new DataTable("dt2"); dt2 = dt.Clone(); SheetOptions sheet2thread = new SheetOptions(worksheet, iColCount, b2 + 2, b2 * 2 + 1, dt2); Thread othread2 = new Thread(new ThreadStart(sheet2thread.SheetToDataTable)); othread2.Start(); DataTable dt3 = new DataTable("dt3"); dt3 = dt.Clone(); SheetOptions sheet3thread = new SheetOptions(worksheet, iColCount, b2 * 2 + 2, b2 * 3 + 1, dt3); Thread othread3 = new Thread(new ThreadStart(sheet3thread.SheetToDataTable)); othread3.Start(); DataTable dt4 = new DataTable("dt4"); dt4 = dt.Clone(); SheetOptions sheet4thread = new SheetOptions(worksheet, iColCount, b2 * 3 + 2, b2 * 4 + 1, dt4); Thread othread4 = new Thread(new ThreadStart(sheet4thread.SheetToDataTable)); othread4.Start(); //主线程读取剩余数据 for (int iRow = b2 * 4 + 2; iRow <= iRowCount; iRow++) { DataRow dr = dt.NewRow(); for (int iCol = 1; iCol <= iColCount; iCol++) { range = (Excel.Range)worksheet.Cells[iRow, iCol]; cellContent = (range.Value2 == null) ? "" : range.Text.ToString(); dr[iCol - 1] = cellContent; } dt.Rows.Add(dr); } othread1.Join(); othread2.Join(); othread3.Join(); othread4.Join(); //将多个线程读取出来的数据追加至 dt1 后面 foreach (DataRow dr in dt.Rows) dt1.Rows.Add(dr.ItemArray); dt.Clear(); dt.Dispose(); foreach (DataRow dr in dt2.Rows) dt1.Rows.Add(dr.ItemArray); dt2.Clear(); dt2.Dispose(); foreach (DataRow dr in dt3.Rows) dt1.Rows.Add(dr.ItemArray); dt3.Clear(); dt3.Dispose(); foreach (DataRow dr in dt4.Rows) dt1.Rows.Add(dr.ItemArray); dt4.Clear(); dt4.Dispose(); return dt1; } else { for (int iRow = 2; iRow <= iRowCount; iRow++) { DataRow dr = dt.NewRow(); for (int iCol = 1; iCol <= iColCount; iCol++) { range = (Excel.Range)worksheet.Cells[iRow, iCol]; cellContent = (range.Value2 == null) ? "" : range.Text.ToString(); dr[iCol - 1] = cellContent; } dt.Rows.Add(dr); } } wath.Stop(); TimeSpan ts = wath.Elapsed; //将数据读入到DataTable中——End return dt; } catch { return null; } finally { workbook.Close(false, oMissiong, oMissiong); System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook); System.Runtime.InteropServices.Marshal.ReleaseComObject(sheets); workbook = null; app.Workbooks.Close(); app.Quit(); System.Runtime.InteropServices.Marshal.ReleaseComObject(app); app = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
(3)NPOI方式讀取Excel(此方法未經過測試)
NPOI 是 POI 專案的 .NET 版本。 POI是一個開源的Java讀寫Excel、WORD等微軟OLE2組件文件的專案。使用 NPOI 你就可以在沒有安裝 Office 或對應環境的機器上對 WORD/EXCEL 文件進行讀寫。
優點:讀取Excel速度較快,讀取方式操作彈性
缺點:需要下載對應的外掛程式並加入系統參考當中。
/// <summary> /// 将excel中的数据导入到DataTable中 /// </summary> /// <param name="sheetName">excel工作薄sheet的名称</param> /// <param name="isFirstRowColumn">第一行是否是DataTable的列名</param> /// <returns>返回的DataTable</returns> public DataTable ExcelToDataTable(string sheetName, bool isFirstRowColumn) { ISheet sheet = null; DataTable data = new DataTable(); int startRow = 0; try { fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); if (fileName.IndexOf(".xlsx") > 0) // 2007版本 workbook = new XSSFWorkbook(fs); else if (fileName.IndexOf(".xls") > 0) // 2003版本 workbook = new HSSFWorkbook(fs); if (sheetName != null) { sheet = workbook.GetSheet(sheetName); } else { sheet = workbook.GetSheetAt(0); } if (sheet != null) { IRow firstRow = sheet.GetRow(0); int cellCount = firstRow.LastCellNum; //一行最后一个cell的编号 即总的列数 if (isFirstRowColumn) { for (int i = firstRow.FirstCellNum; i < cellCount; ++i) { DataColumn column = new DataColumn(firstRow.GetCell(i).StringCellValue); data.Columns.Add(column); } startRow = sheet.FirstRowNum + 1; } else { startRow = sheet.FirstRowNum; } //最后一列的标号 int rowCount = sheet.LastRowNum; for (int i = startRow; i <= rowCount; ++i) { IRow row = sheet.GetRow(i); if (row == null) continue; //没有数据的行默认是null DataRow dataRow = data.NewRow(); for (int j = row.FirstCellNum; j < cellCount; ++j) { if (row.GetCell(j) != null) //同理,没有数据的单元格都默认是null dataRow[j] = row.GetCell(j).ToString(); } data.Rows.Add(dataRow); } } return data; } catch (Exception ex) { Console.WriteLine("Exception: " + ex.Message); return null; } }
更多C#讀取Excel的三種方式以及比較分析相關文章請關注PHP中文網!