透過 OleDb 精準讀入 Excel 檔的方法

分享到噗浪!

之前因為有個案子要做 Excel 匯入的功能,需要讓客戶先下載匯入檔案範本,然後讓客戶上傳 Excel 檔 ( *.xls ),再透過 C# 讀取資料後存入資料庫,我是採用 OleDb 的方式在 Server 端開啟檔案並將資料讀出,不過卻遇到了幾個難解的問題,其中最討厭的問題就是透過 OleDb 載入資料時,它都會自動判斷 Excel 中每個欄位的型別,假設工作表中的第三欄的前 8 列的值是「數字」,而第 9 列的「文字」的話,當讀取到第 3 欄第 9 列的時候,該儲存格的欄位值就會是 Null,可能會引發程式執行錯誤(因為你會預期有資料)。

這又是一個不認真讀書、找資料的典範啦,我當初在寫的時候是有找到一些資料,不過卻沒認真看完,網路上隨便抓一段 Sample Code 就開始寫了(我相信大部分的人都這樣),而當遇到問題的時候就開始直覺的反應 "這怎麼可能" 、 "又是微軟的 Bug" 、 "天阿, OleDb 真難用" 等等(髒話的部分已經刪除),等在內心抱怨完之後(大約兩秒)就開始發揮創意想解決方案(這也是最好玩的部分),當然沒有無法解決的問題,我還是想到了一個當初自己覺得還蠻不錯的方法。(現在覺得很爛,勿學)

為了確保讀出的資料全部都是「文字」,我自己手動建立了一個 Typed DataSet,並將所有欄位都設定成 string,然後透過 OldeDb 將 Excel 資料讀出後存入 Typed DataTable,但這樣還是會發生資料為 Null 的情形,所以我又修改 Excel 匯入檔案範本加入一段 VBA 程式碼,讓客戶在 Excel 中輸入完文字後按下某個按鈕,強迫將所有欄位格式先轉成文字,反正就是一整個囉唆啦。

不過為了寫出這篇文章,我把之前找的文章仔細的看過一遍了,才知道我之前的 "解決方案" 實在是太蠢了,以下是比較聰明的解法。

一般來說,透過 OleDb 載入 Excel 檔案時,設定的連線參數如下:

string _connstr = "Provider=Microsoft.Jet.OLEDB.4.0;" + 
    "Data Source=C:\\MyExcel.xls;" + 
    "Extended Properties=\"Excel 8.0;HDR=Yes;IMEX=1\"";

其中,最關鍵的部分就是在 Extended Properties 的地方,我逐一介紹:

  1. Excel 8.0

    Excel 8.0 是 Office 97 的 Excel 格式,這個格式從 Excel 97 ~ Excel 2003 都相容,如果你在 Excel 中使用「另存新檔」的話,必須要選取這個檔案類型儲存,才能正確用 OleDb 正確開啟,如下圖示:
    Excel 另存新檔所儲存的類型 
  2. HDR ( HeaDer Row )

    若指定值為 Yes,代表 Excel 檔中的工作表第一列是欄位名稱
    若指定值為 No,代表 Excel 檔中的工作表第一列就是資料了,沒有欄位名稱
  3. 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 檔案可同時支援「讀取」與「寫入」用途。

所以當我們要開發 Excel 檔案匯入功能時,正確的 IMEX 設定應該是 1 才對,不過就算設定 IMEX=1 還不夠,還有一些需要注意的地方!

你可以開啟 regedit 程式,選取進系統機碼(Registry)的 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\3.5\Engines\Excel 位置,這裡有兩個非常重要的設定:

系統機碼(Registry)的 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\3.5\Engines\Excel

在 Excel 機碼中 ImportMixedTypes 的預設值為 Text,代表當你讀入 Excel 檔案時若每一列的資料格式不一致的話,Jet Engine 預設會將欄位的資料格式自動轉成文字(Text)格式!

在 Excel 機碼中 TypeGuessRows 的預設值為 8,代表當你讀入 Excel 檔案時若每一列的資料格式不一致的話,Jet Engine 會先讀取前 8 列的資料,用已判斷之後的資料格式是否固定。假設若前 8 列的資料都是「日期」格式,那麼在第 8 列之後的資料 Jet Engine 會自動解析成「日期」格式,但只要遇到有的資料不是「日期」格式時,就會回傳 Null 給 Jet Engine,也就是本篇文章一開始的問題。

我們是提供 Excel 檔案範本給客戶沒錯,但是強大的 Excel 實在太好用了,導致客戶要怎麼用你根本管不著,原本設定好的格式經過客戶複製、剪貼後,格式就大亂了,導致匯入程式就很容易出錯,而最可惡的就是在 Excel 中明明就有資料,而匯入到資料庫之後卻有些資料是空的,這..這..這..太難跟客戶解釋了!(這時候不得不搬出程式設計師最常說的話排行榜)。

要解決這個問題也很容易,只要將 Excel 機碼中 TypeGuessRows 的值改成 0 即可,只是若你匯入的資料量蠻大的話可能會影響資料讀取時的效能,因為 Jet Engine 會將所有資料讀完才會判斷出每個欄位應該用什麼資料格式。

最後提醒,若你的 Excel 檔案有在 Office 中做加密保護的話是無法透過 OleDb 讀取的。

相關連結

  

此文章由 will 發表於 2008/8/5 下午 02:11:17

永久連結 | 評論 (24) | 此文章的RSSRSS comment feed |

分類: .Net | C# | Office | Tips | VBA

標籤: , , ,

評論

八月 6. 2008 09:36

gattaca

這篇相當實用!
"...Excel 中明明就有資料,而匯入到資料庫之後卻有些資料是空的..."

先前被我'呼巄'的客戶,
偷偷向你們道歉!!

gattaca 台灣

十月 13. 2008 11:37

genewu

我有段讀取excel資料的web程式如下: 在Local(XP PRO)可正常執行,但程式copy至 web server(win2000 server)上執行到
myexconn.open()時就出現 "System.data.oledb.oleexception:無法指出的錯誤",不知原因為何?(目錄安全性已將Everyone開至最大了)

ls_code = "02"
xls_file = "200808-2.XLS"
Dim prostr As String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Server.MapPath("..\..\EXCEL\" & xls_file) & ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=1;'"
Dim myexconn As OleDbConnection = Nothing
myexconn = New OleDbConnection(prostr)
selstr = "SELECT * FROM [Sheet1$] where scode = '" & ls_scode & "'"
Dim myexcommand As OleDbCommand = New OleDbCommand(selstr, myexconn)
Dim MyexAdapter As OleDbDataAdapter = New OleDbDataAdapter(myexcommand)
myexconn.Open()
ds = New DataSet
MyexAdapter.Fill(ds, "dtexcel")
myexconn.Close()

genewu 阿根廷

十月 13. 2008 12:23

will

我不太你的問題發生的原因,建議你可以到「微軟技術社群討論區」的「ASP.NET 與 AJAX(ASP.NET and AJAX)」討論區發問:
http://tinyurl.com/msdnaspnet

will 台灣

十月 19. 2008 13:16

yuyu

hi,genewu:

你的問題通常是少做了uploadfile的動作
xls_file要先upload到server,再做匯入的動作
不然再web架構下,server的程式會找不到檔案

yuyu 台灣

一月 26. 2009 21:36

jetcat

可以試試看:http://code.google.com/p/excellibrary/

直接讀寫BIFF8格式

jetcat 台灣

二月 6. 2009 15:57

馬小可

這篇實在是太棒了~~~~~~~~~~~~~
尤其 "...Excel 中明明就有資料,而匯入到資料庫之後卻有些資料是空的..."
解決了我痛苦的問題

馬小可 台灣

二月 9. 2009 15:44

馬小可

HI!!保哥,我想把這篇文章引用到我的部落格,不知您是否同意呢^^?

馬小可 台灣

二月 9. 2009 16:32

will

文章引用是沒問題的,但記得要不要全文轉貼並留下文章來源網址即可。

will 台灣

六月 4. 2009 23:27

tigerkin

你可以開啟 regedit 程式,選取進系統機碼(Registry)的 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\3.5\Engines\Excel 位置,這裡有兩個非常重要的設定!

非常感谢您滴说明,刚好遇到这个问题,再次感谢!

tigerkin 中華人民共和國

五月 13. 2010 13:08

難得清閒

感謝分享
請問大概多大的量的時候會造成效能上的顯著影響呢?
我大概上傳的資料都是5000~20000筆,偶而會有接近50000筆的資料

難得清閒

五月 13. 2010 15:50

Samuel

您好,看了您的教學後,獲益良多~
但也一個問題想請教您一下.
您在讀取Excel的工作表時是固定為sheet1.
如果我今天有多張表格,也因為不知使用著會取什麼工作表名稱.
但我只希望能唯一取得第一張的工作表.
請問您知道有任何方法可以運作嗎?

以上,謝謝您費心的回答.

Samuel 台灣

五月 14. 2010 08:21

難得清閒

To Samuel:
只取得第一張工作表的方式我只有想到用GetSchemaTable()
不過取回來的table是用建立sheet的先後順序排列的...也就是說最左邊的不一定在最前面
不然就是用POI了。

難得清閒

五月 14. 2010 12:38

Will 保哥

Samuel:

我也是推薦使用 NPOI 讀取 Excel 內容,網址如下:
http://npoi.codeplex.com/

Will 保哥 台灣

五月 25. 2010 14:01

emmy

請問一下  怎麼使用ASP.NET上傳EXCEL 然後EXCEL資料會辨識欄位匯入SQL SEVER 2008中

emmy 台灣

五月 25. 2010 14:41

will

emmy:

作法有很多種,可以用此文章 OleDB 的方式、也可以用 NPOI,但這些功能都要自己寫,你的問題範圍太大,建議你可以到 MSDN 論壇發問。

will

八月 10. 2010 07:55

Keynes

您好:

   如果遇的問題不是NULL, 而是數值太長變為科學符號 EX:9.65365e+007 ,會有建議的解決方法嗎?

Keynes 台灣

八月 12. 2010 01:24

Will 保哥

Keynes: 你的問題是什麼?要解決什麼問題?

Will 保哥 台灣

八月 17. 2010 10:48

andydog

保哥你好:

我也遇到Keynes相同的問題,希望將某欄位的數字資料當成文字處理,
例如欄位A的儲存格格式全都設成文字,在A2、A3輸入1234567,不同之處是A2儲存格的左上角有個綠色小三角,說明著[此儲存格內的數字其格式為文字或開頭為單引號],
使用你所提供的連線參數,也改了Excel機碼,在透過OleDb載入Excel檔案時,
A2正確讀到1234567,A3卻變成1.23457e+006 科學記號表示法,
請問有什麼方法可以都正確讀成文字呢?
在還沒有解法之前,只好在Excel檔寫個巨集將數值資料前頭加上單引號。

附註.我直接開啟Excel檔案並另存成XML試算表,
發現A2為<Data ss:Type="String">1234567</Data>,
而A3為<Data ss:Type="Number">1234567</Data>

andydog 台灣

八月 20. 2010 14:13

Erwin

保哥,您好:
在此有個問題請教一下,就是將excel檔案匯入至SQL Server資料庫時,難免會有錯誤出現以至於無法順利匯入至資料庫裡,那有什麼辦法可以透過哪一個系統參數,準確地抓到excel檔案裡哪一行(rows)的資料有錯,這樣在匯入時就可透過訊息馬上回應使用者修正excel檔案的內容,謝謝!感激不盡!

Erwin 台灣

八月 20. 2010 17:09

Will 保哥

Erwin: 你在跑迴圈的時候應該就可以判斷是哪一行出錯了吧?

Will 保哥 台灣

八月 20. 2010 18:34

andydog

保哥你好:
從XML試算表來看,儲存格格式是當Cell值為數字時要如何呈現,例如設成文字,則XMl會是
<Style ss:ID="s28">
   <NumberFormat ss:Format="@"/>
</Style>
我不能理解的是為何相同的Cell值,卻會存成不同的型別:
  <Data ss:Type="String">1234567</Data>
  <Data ss:Type="Number">1234567</Data>
而OleDb在讀取資料時就會把 ss:Type="Number" 且數字過大的值變成科學記號表示法,
實在很不願意在Excel檔寫個巨集將數值資料前頭加上單引號,這問題真是令人苦惱。

andydog 台灣

八月 20. 2010 19:26

Will 保哥

andydog: 我已經很久沒用 OleDb 來讀取資料了,建議你改用 NPOI 或 Open XML SDK 來開發。

Will 保哥 台灣

八月 22. 2010 02:46

andydog

保哥你好:
謝謝你的建議,很感謝.

andydog 台灣

九月 1. 2010 13:00

Bruce

Thanks, very useful information.

Bruce 台灣

新增評論


( 您輸入的Email不會顯示於網站上 )

  Country flag

biuquote
  • 評論
  • 線上預覽
Loading