這篇文章主要介紹了C#讀取Excel的三種方式以及比較分析,需要的朋友可以參考下
(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 參數了,因為不同的模式代表著不同的讀寫行為:
當 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;
- }
- }
下面是一些相關的文章,大家可以參考下
新聞熱點
疑難解答