近日突發奇想,封裝一個Excel的幫助類,好讓日后做一些Excel操作時方便一點,至少導入導出會方便點吧。不過在封裝過程中發現自己太差勁了,問題多多,搞這么百來行代碼花了很長時間,於是寫篇日志,記錄一下這個ExcelHelper,也順便記錄一下封裝過程中遇到的一些問題。
整個Helper中包括了讀和寫兩部分,讀是利用ADO.NET的OleDB進行讀,與查詢SQL Server很相像,查詢語句是這種形式
SELECT * FROM [Sheet1$A1:A10]
“$”符號后面可以加一個范圍,表明要讀取哪一部分,如果不加的話就表明全表讀取了。
下面則是讀那部分的方法,一個是通用的查詢,另一個則是導入
1 /// <summary> 2 /// 執行SQL查詢一個Excel文檔的內容 3 /// </summary> 4 /// <param name="fileName">Excel文件名</param> 5 /// <param name="cmdText">要執行的SQL 區域選擇用 [Sheet1$A1:C7]形式</param> 6 /// <param name="paramters">查詢參數</param> 7 /// <returns>查詢結果</returns> 8 public static DataTable ExecuteReader(string fileName, string cmdText, params OleDbParameter[] paramters) 9 { 10 string strCon = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = " + fileName + ";Extended Properties='Excel 8.0;HDR=NO;IMEX=1';"; 11 OleDbConnection cn = null; 12 OleDbCommand cmd = new OleDbCommand(); 13 OleDbDataReader reader = null; 14 DataTable table = new DataTable(); 15 try 16 { 17 cn = new OleDbConnection(strCon); 18 PrepareCommand(cmd, cn, cmdText, paramters); 19 reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); 20 table.Load(reader); 21 cmd.Parameters.Clear(); 22 } 23 finally 24 { 25 if (reader != null) 26 { 27 reader.Close(); 28 reader.Dispose(); 29 } 30 if (cn != null) 31 { 32 cn.Close(); 33 cn.Dispose(); 34 } 35 } 36 return table; 37 }
1 /// <summary> 2 /// 把Excel的某個工作表導入到DataTable中 3 /// </summary> 4 /// <param name="fileName">文件完整路徑</param> 5 /// <param name="sheetName">工作表名</param> 6 /// <param name="HasHeader">是否存在表頭。若是會把工作表第一行轉成DataTable的列名</param> 7 /// <returns>導入后的DataTable</returns> 8 public static DataTable ImportExcel(string fileName, string sheetName, bool HasHeader) 9 { 10 DataTable table = ExecuteReader(fileName, "select * from [" + sheetName + "]", null); 11 12 if (HasHeader) 13 { 14 for (int i = 0; i < table.Columns.Count; i++) 15 table.Columns[i].ColumnName = table.Rows[0][i].ToString(); 16 table.Rows.RemoveAt(0); 17 } 18 19 return table; 20 }
其實導入也是調用了通用查詢的方法,不過加了一點表頭的處理罷了。
寫則是利用了一個COM組件,需要添加一個dll應用,Microsoft.Office.Interop.Excel.dll。原本也想用ADO.NET的,但是那寫INSERT 和 UPDATE的SQL我確實寫不好,老拋異常,於是放棄了,改用COM,不過用COM好像還挺直觀的。
下面也粘兩段代碼,一個是編輯Excel文件的(其實就涵蓋了原計划中的UPDATE和INSERT的操作而已,對於整行的刪除,整列的刪除,合並單元格等操作,鄙人還沒做到。或許以后會補充上去。)另一個是導出到Excel文件。在這部分中重命名了Microsoft.Office.Interop.Excel這個明明空間,鄙人參考網友把它重命名為Excel了。
1 /// <summary> 2 /// 編輯一個Excel文檔 3 /// </summary> 4 /// <param name="fileName">Excel文件名</param> 5 /// <param name="table">要編輯的內容</param> 6 /// <param name="startCell">目標單元格位置</param> 7 public static void EditExcel(string fileName, DataTable table, string startCell) 8 { 9 Tuple<int, int> cell = ConvertCell(startCell); 10 11 object missing = System.Reflection.Missing.Value; 12 Excel.Application app = new Microsoft.Office.Interop.Excel.ApplicationClass(); 13 app.Application.Workbooks.Add(true); 14 15 Excel.Workbook book = null; 16 Excel.Worksheet sheet = null; 17 bool existFile=IsExistFile(fileName); 18 if (existFile) 19 { 20 book=app.Workbooks.Open(fileName, 0, false, 5, "", "", true, Excel.XlPlatform.xlWindows, "\t", false, false, 0, true); 21 } 22 else { 23 book = (Excel.Workbook)app.ActiveWorkbook; 24 } 25 sheet = (Excel.Worksheet)book.ActiveSheet; 26 27 for (int i = 0,ei=cell.Item1; i < table.Rows.Count; i++,ei++) 28 for (int j = 0, ej = cell.Item2; j < table.Columns.Count; j++, ej++) 29 sheet.Cells[ei, ej] = table.Rows[i][j]; 30 31 32 if(existFile) book.Save(); 33 else book.SaveCopyAs(fileName); 34 //關閉文件 35 book.Close(false, missing, missing); 36 //退出excel 37 app.Quit(); 38 39 }
1 /// <summary> 2 /// 把DataTable導出到一個Excel文件 3 /// </summary> 4 /// <param name="fileName">Excel文件名</param> 5 /// <param name="table">要導出的DataTable</param> 6 /// <param name="AddHeader">是否要增加表頭</param> 7 public static void ExportExcel(string fileName, DataTable table, bool AddHeader) 8 { 9 10 object missing = System.Reflection.Missing.Value; 11 Excel.Application app = new Microsoft.Office.Interop.Excel.ApplicationClass(); 12 app.Application.Workbooks.Add(true); 13 Excel.Workbook book = (Excel.Workbook)app.ActiveWorkbook; 14 Excel.Worksheet sheet = (Excel.Worksheet)book.ActiveSheet; 15 16 if (AddHeader) 17 for (int i = 0; i < table.Columns.Count; i++) 18 sheet.Cells[1, i+1] = table.Columns[i].ColumnName; 19 20 for (int i = 0, ei = AddHeader ? 2 : 1; i < table.Rows.Count; i++, ei++) 21 for (int j = 0; j < table.Columns.Count; j++) 22 sheet.Cells[ei, j+1] = table.Rows[i][j]; 23 24 25 string path = fileName.Substring(0, fileName.LastIndexOf('\\') + 1).Trim('\\'); 26 if (!Directory.Exists(path)) 27 Directory.CreateDirectory(path); 28 29 book.SaveCopyAs(fileName); 30 //關閉文件 31 book.Close(false, missing, missing); 32 //退出excel 33 app.Quit(); 34 }
在這部分中遇到兩個問題:
第一個是調用ApplicationClass()這個構造函數時,編譯會不同過,錯誤信息是“無法嵌入互操作類型”,只要把Microsoft.Office.Interop.Excel.dll的 嵌入互操作類型 屬性設為False就行了。
第二是給交錯數組sheet[,]賦值時,如果兩個下標中任意一個用了0的話,運行時會拋出COMException的,異常信息是 Exception from HRESULT: 0x800A03EC 這是由於Excel的工作表第0行或第0列是Excel工作表的條標尺,如下圖紅框框住的兩條。
因此不可賦值。在循環遍歷時,循環變量則要從1開始。
下面則把整個ExcelHelper的代碼粘出來,歡迎大家批評指正。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Data.OleDb; 6 using System.Data; 7 using System.IO; 8 using Microsoft.Office.Interop; 9 using Excel = Microsoft.Office.Interop.Excel; 10 using System.Text.RegularExpressions; 11 12 namespace MyHelpers.Helpers 13 { 14 public class ExcelHelper 15 { 16 #region 公共方法 17 18 #region 導入 19 20 /// <summary> 21 /// 把Excel的某個工作表導入到DataTable中 22 /// </summary> 23 /// <param name="fileName">文件完整路徑</param> 24 /// <param name="sheetName">工作表名</param> 25 /// <param name="HasHeader">是否存在表頭。若是會把工作表第一行轉成DataTable的列名</param> 26 /// <returns>導入后的DataTable</returns> 27 public static DataTable ImportExcel(string fileName, string sheetName, bool HasHeader) 28 { 29 DataTable table = ExecuteReader(fileName, "select * from [" + sheetName + "]", null); 30 31 if (HasHeader) 32 { 33 for (int i = 0; i < table.Columns.Count; i++) 34 table.Columns[i].ColumnName = table.Rows[0][i].ToString(); 35 table.Rows.RemoveAt(0); 36 } 37 38 return table; 39 } 40 41 /// <summary> 42 /// 把Excel的Sheet工作表導入到DataTable中 43 /// </summary> 44 /// <param name="fileName">文件完整路徑</param> 45 /// <param name="HasHeader">是否存在表頭。若是會把工作表第一行轉成DataTable的列名</param> 46 /// <returns>導入后的DataTable</returns> 47 public static DataTable ImportExcel(string fileName, bool HasHeader) 48 { 49 return ImportExcel(fileName, "sheet1$", HasHeader); 50 } 51 52 /// <summary> 53 /// 把Excel的某個工作表導入到DataTable中 54 /// </summary> 55 /// <param name="fileName">文件完整路徑</param> 56 /// <returns></returns> 57 public static DataTable ImportExcel(string fileName) 58 { 59 return ImportExcel(fileName, true); 60 } 61 62 #endregion 63 64 #region 導出 65 66 /// <summary> 67 /// 把DataTable導出到一個Excel文件,並增加表頭 68 /// </summary> 69 /// <param name="fileName">Excel文件名</param> 70 /// <param name="table">要導出的DataTable</param> 71 public static void ExportExcel(string fileName, DataTable table) 72 { 73 ExportExcel(fileName, table, true); 74 } 75 76 /// <summary> 77 /// 把DataTable導出到一個Excel文件 78 /// </summary> 79 /// <param name="fileName">Excel文件名</param> 80 /// <param name="table">要導出的DataTable</param> 81 /// <param name="AddHeader">是否要增加表頭</param> 82 public static void ExportExcel(string fileName, DataTable table, bool AddHeader) 83 { 84 85 object missing = System.Reflection.Missing.Value; 86 Excel.Application app = new Microsoft.Office.Interop.Excel.ApplicationClass(); 87 app.Application.Workbooks.Add(true); 88 Excel.Workbook book = (Excel.Workbook)app.ActiveWorkbook; 89 Excel.Worksheet sheet = (Excel.Worksheet)book.ActiveSheet; 90 91 if (AddHeader) 92 for (int i = 0; i < table.Columns.Count; i++) 93 sheet.Cells[1, i+1] = table.Columns[i].ColumnName; 94 95 for (int i = 0, ei = AddHeader ? 2 : 1; i < table.Rows.Count; i++, ei++) 96 for (int j = 0; j < table.Columns.Count; j++) 97 sheet.Cells[ei, j+1] = table.Rows[i][j]; 98 99 100 string path = fileName.Substring(0, fileName.LastIndexOf('\\') + 1).Trim('\\'); 101 if (!Directory.Exists(path)) 102 Directory.CreateDirectory(path); 103 104 book.SaveCopyAs(fileName); 105 //關閉文件 106 book.Close(false, missing, missing); 107 //退出excel 108 app.Quit(); 109 } 110 111 #endregion 112 113 #region 通用讀取 114 115 /// <summary> 116 /// 執行SQL查詢一個Excel文檔的內容 117 /// </summary> 118 /// <param name="fileName">Excel文件名</param> 119 /// <param name="cmdText">要執行的SQL 區域選擇用 [Sheet1$A1:C7]形式</param> 120 /// <param name="paramters">查詢參數</param> 121 /// <returns>查詢結果</returns> 122 public static DataTable ExecuteReader(string fileName, string cmdText, params OleDbParameter[] paramters) 123 { 124 string strCon = " Provider = Microsoft.Jet.OLEDB.4.0 ; Data Source = " + fileName + ";Extended Properties='Excel 8.0;HDR=NO;IMEX=1';"; 125 OleDbConnection cn = null; 126 OleDbCommand cmd = new OleDbCommand(); 127 OleDbDataReader reader = null; 128 DataTable table = new DataTable(); 129 try 130 { 131 cn = new OleDbConnection(strCon); 132 PrepareCommand(cmd, cn, cmdText, paramters); 133 reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); 134 table.Load(reader); 135 cmd.Parameters.Clear(); 136 } 137 finally 138 { 139 if (reader != null) 140 { 141 reader.Close(); 142 reader.Dispose(); 143 } 144 if (cn != null) 145 { 146 cn.Close(); 147 cn.Dispose(); 148 } 149 } 150 return table; 151 } 152 153 /// <summary> 154 /// 執行SQL查詢一個Excel文檔默認工作表Sheet1的內容 155 /// </summary> 156 /// <param name="fileName">Excel文件名</param> 157 /// <param name="cmdText">查詢參數</param> 158 /// <returns>查詢結果</returns> 159 public static DataTable ExecuteReader(string fileName, string cmdText) 160 { 161 return ExecuteReader(fileName, cmdText, null); 162 } 163 164 #endregion 165 166 #region 通用編輯 167 168 /// <summary> 169 /// 編輯一個Excel文檔 170 /// </summary> 171 /// <param name="fileName">Excel文件名</param> 172 /// <param name="table">要編輯的內容</param> 173 /// <param name="startCell">目標單元格位置</param> 174 public static void EditExcel(string fileName, DataTable table, string startCell) 175 { 176 Tuple<int, int> cell = ConvertCell(startCell); 177 178 object missing = System.Reflection.Missing.Value; 179 Excel.Application app = new Microsoft.Office.Interop.Excel.ApplicationClass(); 180 app.Application.Workbooks.Add(true); 181 182 Excel.Workbook book = null; 183 Excel.Worksheet sheet = null; 184 bool existFile=IsExistFile(fileName); 185 if (existFile) 186 { 187 book=app.Workbooks.Open(fileName, 0, false, 5, "", "", true, Excel.XlPlatform.xlWindows, "\t", false, false, 0, true); 188 } 189 else { 190 book = (Excel.Workbook)app.ActiveWorkbook; 191 } 192 sheet = (Excel.Worksheet)book.ActiveSheet; 193 194 for (int i = 0,ei=cell.Item1; i < table.Rows.Count; i++,ei++) 195 for (int j = 0, ej = cell.Item2; j < table.Columns.Count; j++, ej++) 196 sheet.Cells[ei, ej] = table.Rows[i][j]; 197 198 199 if(existFile) book.Save(); 200 else book.SaveCopyAs(fileName); 201 //關閉文件 202 book.Close(false, missing, missing); 203 //退出excel 204 app.Quit(); 205 206 } 207 208 /// <summary> 209 /// 編輯一個Excel文檔 210 /// </summary> 211 /// <param name="fileName">Excel文件名</param> 212 /// <param name="table">要編輯的內容</param> 213 public static void EditExcel(string fileName, DataTable table) 214 { 215 EditExcel(fileName, table, "A1"); 216 } 217 218 #endregion 219 220 #endregion 221 222 #region 私有方法 223 224 /// <summary> 225 /// 檢查文件是否存在,若不存在則會先確保文件所在的目錄存在 226 /// </summary> 227 /// <param name="fileName">文件名</param> 228 /// <returns>檢查結果</returns> 229 private static bool IsExistFile(string fileName) 230 { 231 if(File.Exists(fileName))return true; 232 string path = fileName.Substring(0, fileName.LastIndexOf('\\') + 1).Trim('\\'); 233 if (!Directory.Exists(path)) 234 Directory.CreateDirectory(path); 235 return false; 236 } 237 238 /// <summary> 239 /// 轉換單元格位置 240 /// </summary> 241 /// <param name="cell">單元格位置</param> 242 /// <returns>int二元組</returns> 243 private static Tuple<int, int> ConvertCell(string cell) 244 { 245 Match colM=Regex.Match(cell,@"[a-zA-Z]+"); 246 if (string.IsNullOrEmpty(colM.Value)) 247 throw new Exception("單元格格式有誤!"); 248 string colStr = colM.Value.ToUpper(); 249 int colIndex = 0; 250 foreach (char ci in colStr) 251 colIndex += 1+(ci - 'A'); 252 253 Match rowM = Regex.Match(cell, @"\d+"); 254 if(string.IsNullOrEmpty(rowM.Value)) 255 throw new Exception("單元格格式有誤!"); 256 int rowIndex =Convert.ToInt32( rowM.Value); 257 258 Tuple<int, int> result = new Tuple<int, int>(rowIndex,colIndex); 259 return result; 260 } 261 262 /// <summary> 263 /// 准備OleDbCommand 264 /// </summary> 265 /// <param name="command">查詢命令</param> 266 /// <param name="connection">連接類</param> 267 /// <param name="cmdText">命令內容</param> 268 /// <param name="paramters">查詢參數</param> 269 private static void PrepareCommand(OleDbCommand command, OleDbConnection connection, string cmdText, params OleDbParameter[] paramters) 270 { 271 command.CommandText = cmdText; 272 command.Connection = connection; 273 274 if (paramters != null) 275 foreach (OleDbParameter item in paramters) 276 command.Parameters.Add(item); 277 278 if (connection.State != ConnectionState.Open) 279 connection.Open(); 280 } 281 282 #endregion 283 } 284 }
這里有個連接通向用NPOI封裝的ExcelHelper類: 另一個ExcelHelper