說到Excel,那是叫個怕啊!一吧自己對Excel不熟悉,二吧大家都說Excel不好操作;心里上不知不覺就對它產生了畏懼。但是有句話說的好啊:你最怕的地方就是你最弱的地方。想要進步還是得想辦法提高自己,就去搞自己最怕的!
其實說到怕,也並不是真的怕,而是對這方面不熟悉造成的;想要解決怕的問題,就得把它整明白了,下次在遇見就不怕不怕了;最近幫同學做了個Excel處理的小程序;涉及到了一下Excel的操作,特此給大家分享一下。
1. Excel導入到數據庫的操作
(1)、通過NPOI來實現。NPOI是個好東東,它能方便的讓你對Excel的控制達到單元格級別;而且使用起來也比較方便,不需要依賴office的什么組件;基本上引用了官方提供的那幾個dll就可以使用了。我在使用的過程中遇到了一個問題,NPOI提示缺少一個.dll,叫做:"ICSharpCode.SharpZipLib.dll",后來查了查資料,了解到Excel2007或2010的.xlsx格式的文件其實是壓縮文件,NPOI在處理.xlsx文件的時候需要先解壓縮,而解壓縮就用到了ICSharpCode.SharpZipLib.dll,這是個比較常用的dll,可能系統由於某種原因造成了dll的丟失,然后從網上找了好久才找到合適的dll文件,有需要的童鞋可以點擊"ICSharpCode.SharpZipLib.dll"下載我找的dll試試。
NPOI的具體操作我就不再啰嗦了,大家可以點擊"學習NPOI"去仔細研究;通過NPOI操作Excel是個不錯的選擇,在數據量不是特別大的時候可以考慮使用;因為NPOI在加載Excel的時候需要讀取每個單元格的樣式、格式等信息,所以不太適合大數據量的操作。
(2)、使用SqlBulkCopy類。類描述:使您可以用其他源的數據有效批量加載 SQL Server 表。吊,微軟的提供的類,想必很好用。通過程序測試了下,25W行的數量需要5秒左右;用法我大致說一下,不清楚的可以MSDN查查。
思路:把Excel中的數據提取到內存DataTable中,然后通過SqlBulkCopy類寫入到數據庫,貼代碼:
public static void InsertDBFromExcelData(string excelFile, string tableName, SqlBulkCopyColumnMapping[] mappings) { string mystring = "Provider = Microsoft.Ace.OleDb.12.0 ; Data Source = '{0}';Extended Properties='Excel 12.0; HDR=Yes; IMEX=1'".With(excelFile); OleDbConnection cnnxls = new OleDbConnection(mystring); var allSheetNames = GetSheetNames(excelFile); foreach (string sheetName in allSheetNames) { OleDbDataAdapter myDa = new OleDbDataAdapter("select * from [{0}]".With(sheetName), cnnxls); DataTable dt = new DataTable(); myDa.Fill(dt); if (dt.Rows.Count > 0) { using (SqlBulkCopy copy = new SqlBulkCopy(DBConnectionStr)) //與目標服務器連接 { copy.BulkCopyTimeout = 5000; copy.DestinationTableName = tableName; //導入到數據庫的表名 if (mappings != null) { foreach (var item in mappings) { copy.ColumnMappings.Add(item); } } copy.WriteToServer(dt); } } } }
2.從數據庫導出到Excel
(1)、使用NPOI寫入,具體用法我就不說了;測試的時候,循環寫入25W個單元格大致需要4~8秒,已經相當快了(未設置單元格的樣式等信息)。
(2)、使用利用驅動來實現。比如Sqlserver數據庫,可以這么寫sql語句: SELECT * FROM INTO [SheetName] TABLE in "" [ODBC;Driver=SQL SERVER;Data Source=.;DataBase=sa;Integrated security=true] ;然后這樣寫執行語句:
string cnnString = "Provider = Microsoft.Ace.OleDb.12.0 ; Data Source = 'D:\Files\test.xlsx';Extended Properties='Excel 12.0; HDR=NO; IMEX=0'";
using (OleDbConnection cnnxls = new OleDbConnection(cnnString)) { cnnxls.Open(); OleDbCommand cmd = new OleDbCommand(sql, cnnxls); cmd.ExecuteNonQuery(); cnnxls.Close(); }
看sql語句和執行的鏈接串的寫法,大致也能猜出個什么意思。 Provider = Microsoft.Ace.OleDb.12.0 表示驅動程序是OleDb.12.0,Extended Properties='Excel 12.0; HDR=yes; IMEX=0' 表示的鏈接的擴展屬性:Excel12.0表示的是鏈接的Excel版本,如果是電腦中只安裝了Excel2003,則需修改為:Excel4.0;如果安裝了Excel2007或2010則不用修改了,一樣能處理.xls和.xlsx文件;"HDR=yes;"是說第一行是列名而不是數據,"HDR=no;"正好與前面的相反,把第一行也當成數據處理,這樣導入到數據庫表中的數據就會多出第一行的標題數據(如果你的Excel文件的第一行是標題);另外說明一下IMEX的意義,不寫了,抄了點兒,挺詳細的:
當 IMEX=0 時為“匯出模式”,這個模式開啟的 Excel 檔案只能用來做“寫入”用途。
當 IMEX=1 時為“匯入模式”,這個模式開啟的 Excel 檔案只能用來做“讀取”用途。
當 IMEX=2 時為“連結模式”,這個模式開啟的 Excel 檔案可同時支援“讀取”與“寫入”用途。
意義如下:
0 ---輸出模式;
1---輸入模式;
2----鏈接模式(完全更新能力)
使用該方法可以快速的把Excel中的數據導入到數據庫中,我測試的是10列,25W行數據導入到Excel的時間只需要3秒即可;但是這個方法也有缺點,他不能對Excel的數據進行再加工;例如Excel數據格式的轉變,或者計算列等需要深加工的數據就沒什么辦法了;比如Excel中有一列是數字,但是當導入數據庫時可能就變成了科學表示法的文本了(10+E2),這樣的問題要么改Excel的單元格格式,要么在取的時候轉換一下。
另外遇到了一個奇怪的問題,就是去Excel所有工作簿的名字的問題,有時候明明一個工作簿,但是通過普通的方法總是能取到2個名字,比如:有一個工作簿:mysheet1;但是查詢到會存在兩個:mysheet1,mysheet1$,但其實不存在第二個工作簿。沒有深究,寫了方法干掉了第二個:
public static string[] GetSheetNames(string file) { List<string> names = new List<string>(); string mystring = "Provider = Microsoft.Ace.OleDb.12.0 ; Data Source = '{0}';Extended Properties='Excel 12.0;'".With(file); using (OleDbConnection oleConn = new OleDbConnection(mystring)) { oleConn.Open(); DataTable dtOle = oleConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" }); DataTableReader dtReader = new DataTableReader(dtOle); List<SheetName> allSheetNames = new List<SheetName>(); foreach (DataRow row in dtOle.Rows) { allSheetNames.Add(new SheetName(row)); } allSheetNames = allSheetNames.Distinct(new SheetNameEquatilyCompare()).ToList(); allSheetNames.ForEach(f => { names.Add(f.Name); }); dtOle = null; oleConn.Close(); } return names.Distinct().ToArray(); } //工作簿名稱自定義比較器 class SheetNameEquatilyCompare : IEqualityComparer<SheetName> { public bool Equals(SheetName x, SheetName y) { return x.ModifyTime == y.ModifyTime && x.CreateTime == y.CreateTime && x.Name.TrimEnd('$') == y.Name.TrimEnd('$'); } public int GetHashCode(SheetName obj) { return 10; } }
