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中的所有列都轉為字符型。
CSV的文件,它的連接字符串會讀取該文件所在的文件夾下所有的文本文件數據。
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.GetDirectoryName(filePath)};Extended Properties='Text;FMT=Delimited;HDR={hdr};FMT=Delimited;'"; 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); } }