Excel檔案讀取操作方法
Excel的連線中,由兩個值需要注意。
首先是HDR值,該值指示是否將表中的第一行有效(第一個行資料不為空的行)資料當作標題列處理。如果選擇是YES,那麼通過C#讀取出來的資料表中,表的列標題則是對應的第一行有效資料;否則,將所有資料都當作資料處理,此時以F1、F2……Fn為列標題。預設的是YES,見後續的使用。
其次是IMEX模式,IMEX 有三種模式,不同的模式代表著不同的讀寫行為:
- 0 is Export mode:為“匯出模式”,這個模式開啟的 Excel 檔案只能用來做“寫入”用途。 ---輸出模式;
- 1 is Import mode:為“匯入模式”,這個模式開啟的 Excel 檔案只能用來做“讀取”用途。---輸入模式,始終將“互混”資料列作為文字讀取;
- 2 is Linked mode (full update capabilities):為“連結模式”,這個模式開啟的 Excel 檔案可同時支援“讀取”與“寫入”用途。---連結模式(完全更新能力,效率不高)
IMEX=1的作用是,當讀取Excel中每個單元格的值到DataTable中的時候,不管其在Excel單元格時候是什麼資料型別,賦值到DataTable中都強制轉化為字串型別。
在沒有IMEX=1這個屬性的時候,預設的是根據Excel中對應Column的資料型別來決定DataTable中Column的資料型別。這種情況在Excel中某一列的資料型別都是一致的情況下沒有問題,是什麼型別,就會在DataTable中的對應列設定相應的型別。但是如果Excel中這一列的型別混亂的話,比如說既包括數值型又有字串型,在執行時建立DataTable的時候,會去先判斷Excel中這一列哪種型別的資料佔主體,然後給DataTable的列設定為這種型別。比如說,如果一列中既有整數型又有字元型,而整數型單元格佔主體,這時DataTable中的列就是整數型。
這時又出現另外一個問題,當要把值寫入到DataTable的時候,如果該單元格符合DataTable中要求的型別,就會寫入,如果不符合的話,系統會去強制轉換。比如,如果Excel中是字串的5,而該單元格所在的列整數型佔主體,DataTable中這一列是數值型,這時,系統會把字串的5強制轉為數值型的5然後賦給DataTable相應的地方。但是,此時,如果轉換有問題的話,比如,此列中有一單元格中是“NO5”,這時強制轉換就會有問題,系統就會給DataTable相應的地方賦值DBNull,現在如果你用DataGridView來顯示那個DataTable的時候,這個地方顯示出來就是一個空白的格,但要注意的是,並不是DataTable中的這一行這一列是null,而是DBNull.其實,我覺得,跟null的作用是一樣的,反正在DataGridView中是不會顯示出來。只是我們在寫程式如果需要對DataTable的null元素篩選的話,需要注意這個問題。
如果Excel中,某一行字串型別佔主體的話,那麼DataTable中這一列就會設定為字串型,而且任何型別都能順利轉換成字串型別,所以,Excel的類會完整的顯示出來,不管這一列中的字串型別的單元格,還是整數型的單元格,都能完整的顯示出來。這是一很特別的地方。但這僅僅是一個特例。
所以,如果為了處理的時候資料型別的一致性,如果Excel中資料型別混亂的話,可以使用IMEX=1使DataTable中的所有列都轉為字元型。
ExcelReader程式碼:
/// <summary> /// 連線Excel的模式 /// </summary> public enum IMEX { /// <summary> /// 匯出(輸出)模式 /// </summary> Export = 0, /// <summary> /// 匯入(輸入)模式 /// </summary> Import = 1, /// <summary> /// 連結模式(完全更新能力) /// </summary> Linked = 2, } public sealed class ExcelReader { /// <summary> /// 是否將第一行資料作為標題列。如果選擇no, 則將第一行資料作為資料讀取,則預設F1~開始為標題 /// </summary> public bool IsFirstRowAsColumnNames { get; set; } = true; /// <summary> /// 連線Excel的模式 /// </summary> public IMEX IMEX { get; set; } = IMEX.Import; /// <summary> /// 檔案的絕對路徑 /// </summary> string filePath = string.Empty; /// <summary> /// 構造物件 /// </summary> /// <param name="hdr">第一行的讀取模式:預設是Yes</param> /// <param name="imex"></param> public ExcelReader(IMEX imex = IMEX.Import) { IMEX = imex; } FileType fileType = FileType.noset; /// <summary> /// 獲取連線字串 /// </summary> /// <param name="fileFullPath">檔案的絕對路徑</param> /// <returns>連線字串</returns> public string GetConnectString(string fileFullPath) { fileType = FileType.noset; if (!File.Exists(fileFullPath))//判斷檔案是否存在 { throw new FileNotFoundException(fileFullPath); } string hdr = IsFirstRowAsColumnNames ? "YES" : "NO"; string connString = string.Empty; filePath = fileFullPath; switch (Path.GetExtension(filePath)) { case ".xls": connString = $"Provider=;Data Source={ filePath }; Extended Properties='Excel 8.0;HDR={hdr};IMEX={(int)IMEX};'"; fileType = FileType.xls; break; case ".xlsx": connString = $"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={ filePath };Extended Properties='Excel 12.0;HDR={hdr};IMEX={(int)IMEX};'"; fileType = FileType.xlsx; break; case ".csv"://filePath.Remove(filePath.LastIndexOf("\\") + 1) connString = $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={Path.GetFileNameWithoutExtension(filePath)};Extended Properties='Text;FMT=Delimited;HDR={hdr};IMEX={(int)IMEX};'"; fileType = FileType.csv; break; } return connString; } /// <summary> /// 開啟連線 /// </summary> /// <param name="fileFullPath">檔案的絕對路徑</param> /// <returns>連線物件</returns> public OleDbConnection GetConnection(string fileFullPath) { string connectString = GetConnectString(fileFullPath);//獲取檔案連線字串 if (fileType == FileType.noset) { throw new ArgumentException("Incorrect file type."); } OleDbConnection oleDb = new OleDbConnection(); try { oleDb.ConnectionString = connectString; } catch (Exception ex) { oleDb.Close(); throw ex; } return oleDb; } /// <summary> /// 獲取所有表單的名稱;注意,表單名順序不一定是Excel中顯示的順序,是新增表時的先後順序 /// </summary> /// <param name="fileFullName">檔案全名</param> /// <returns>表單名稱</returns> public List<string> GetSheetsName(string fileFullName) { OleDbConnection connection = GetConnection(fileFullName);//獲取連線物件 List<string> lstSheets = GetSheetsName(connection); return lstSheets; } /// <summary> /// 獲取所有表單的名稱 /// </summary> /// <param name="connection">連線物件</param> /// <returns>表單名稱</returns> public List<string> GetSheetsName(OleDbConnection connection) { List<string> lstSheets = new List<string>(); try { if (connection.State != ConnectionState.Open) { connection.Open(); } DataTable dt = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);//獲取表單 for (int i = 0; i < dt.Rows.Count; i++) { lstSheets.Add(dt.Rows[i]["Table_Name"].ToString()); } } finally { connection.Close(); } return lstSheets; } /// <summary> /// 獲取檔案內的所有Table資料集合 /// </summary> /// <param name="fileFullName">檔案全名</param> /// <returns>資料集合</returns> public DataSet GetDataSet(string fileFullName) { OleDbConnection connection = GetConnection(fileFullName);//獲取連線物件 return GetDataSet(connection); } /// <summary> /// 獲取檔案內的所有Table資料集合 /// </summary> /// <param name="connection">連線物件</param> /// <returns>資料集合</returns> public DataSet GetDataSet(OleDbConnection connection) { DataSet ds = new DataSet(); try { List<string> lstSheets = GetSheetsName(connection); if (connection.State != ConnectionState.Open) { connection.Open(); } OleDbDataAdapter adapter = new OleDbDataAdapter(connection.CreateCommand());//建立讀取資料 for (int i = 0; i < lstSheets.Count; i++) { adapter.SelectCommand.CommandText = string.Format("SELECT * FROM [{0}]", lstSheets[i].Trim('/'));//查詢字串 adapter.Fill(ds);//填充資料 ds.Tables[i].TableName = lstSheets[i];//賦值表名 } adapter.Dispose(); } finally { connection.Close(); } return ds; } /// <summary> /// 獲取檔案內的所有Table資料集合 /// </summary> /// <param name="fileFullName">檔案全名</param> /// <param name="tableName">表名</param> /// <returns>資料集合</returns> public DataTable GetDataTable(string fileFullName, string tableName) { OleDbConnection connection = GetConnection(fileFullName);//獲取連線物件 return GetDataTable(connection, tableName); } /// <summary> /// 獲取檔案內的所有Table資料集合 /// </summary> /// <param name="connection">連線物件</param> /// <param name="tableName">表名</param> /// <returns>資料集合</returns> public DataTable GetDataTable(OleDbConnection connection, string tableName) { DataSet ds = new DataSet(); try { if (!tableName.EndsWith("$"))//檢查sheet名稱是否是以'$'結尾的,必須以'$'結尾 { tableName += '$'; } connection.Open(); OleDbCommand cmd = connection.CreateCommand(); cmd.CommandText = $"Select * from [{tableName}]";//必須加方括號 using (OleDbDataReader reader = cmd.ExecuteReader()) { ds.Load(reader, LoadOption.OverwriteChanges, tableName);//這兒使用的是DataSet的載入方法 } } finally { connection.Close(); } if (ds.Tables.Count > 0) { return ds.Tables[0]; } else { return null; } } /// <summary> /// 通過sql語句獲取表單 /// </summary> /// <param name="fileFullPath">檔案全路徑名稱</param> /// <param name="sql">sql語句</param> /// <returns>通過sql查詢到的表單</returns> /// <remarks> /// 如果HDR使用的是NO, 則預設F1型別開始為列標題(相當於資料庫的欄位名); /// 如果HDR使用的是Yes,則第一行的資料值當作資料庫的欄位名型別 /// string sql="select F1,F2 from [Sheet1$] ";//注意表名稱[] 和 $ 都不能少了 /// </remarks> public DataTable GetDataTableBySql(string fileFullPath, string sql) { OleDbConnection connection = GetConnection(fileFullPath); return GetDataTableBySql(connection, sql); } /// <summary> /// 通過sql語句查詢表單 /// </summary> /// <param name="connection">連線物件</param> /// <param name="sql">查詢語句</param> /// <returns>通過sql查詢到的表單</returns> /// <remarks> /// 如果HDR使用的是NO, 則預設F1型別開始為列標題(相當於資料庫的欄位名) /// string sql="select F1,F2 from [Sheet1$] ";//注意表名稱[] 和 $ 都不能少了 /// </remarks> public DataTable GetDataTableBySql(OleDbConnection connection, string sql) { DataSet ds = new DataSet(); try { connection.Open(); OleDbDataAdapter adapter = new OleDbDataAdapter(sql, connection); adapter.Fill(ds); } finally { connection.Close(); } if (ds.Tables.Count > 0) { return ds.Tables[0]; } else { return null; } } /// <summary> /// 檔案型別 /// </summary> private enum FileType { noset, xls, xlsx, csv } }
ExcelReader的使用:
private void button1_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "Execl files (*.xlsx)|*.xlsx|(*.xls)|*.xls*"; if(ofd.ShowDialog()== DialogResult.OK) { ExcelReader excel = new ExcelReader(); List<string> lstSheets = excel.GetSheetsName(ofd.FileName);//獲取所有表名 dataGridView1.DataSource = lstSheets; DataSet ds0= excel.GetDataSet(ofd.FileName);//獲取Excel檔案中的所有資料 DataTable table = excel.GetDataTable(ofd.FileName,lstSheets.First());//讀取Excel中第一張Sheet的值 //excel.IsFirstRowAsColumnNames =true; //string sql = $"select 學號,姓名,性別,備註 from [{lstSheets[0]}] where 姓名='張三'"; excel.IsFirstRowAsColumnNames = false; //string sql =$"select F1,F2,F3,F6 from [{lstSheets[0]}] where F2='張三'";//查詢張三 string sql = $"select * from [{lstSheets[0]}]";//查詢張三 dataGridView1.DataSource = excel.GetDataTableBySql(ofd.FileName, sql); } }