(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; } }</div>
下面说明一下连接字符串
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); }</div>
因为读取出来无效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">