最近因為各方面原因換了一份工作,去了一家主營物聯櫃的公司,有意思的是物聯櫃上的終端是用 wpf 寫的,代碼也算是年久失修,感覺技術債還是蠻重的,前幾天在調試一個bug的時候,看到了一段類似這樣的代碼:
是不是很眼熟哈,或許你也已經多年不見了,猶記得那時候為了能從資料庫擷取資料,第一種方法就是采用 <code>SqlDataReader</code> 一行一行從資料庫讀取,而且還要操心 Reader 的 close 問題,第二種方法為了避免麻煩,就直接使用了本篇說到的 <code>SqlDataAdapter</code> ,簡單粗暴,啥也不用操心,對了,不知道您是否和我一樣對這個 <code>Fill</code> 方法很好奇呢?,它是如何将資料塞入到 <code>DataTable</code> 中的呢? 也是用的 SqlDataReader 嗎? 而且 <code>Fill</code> 還有好幾個擴充方法,哈哈,本篇就逐個聊一聊,就當回顧經典啦!
dnspy小工具大家可以到GitHub上面去下載下傳一下,這裡就不具體說啦,接下來追一下Fill的最上層實作,如下代碼:
上面的代碼比較關鍵的一個地方就是 <code>IDbCommand selectCommand = this._IDbDataAdapter.SelectCommand;</code> 這裡的 SelectCommand 來自于哪裡呢? 來自于你 new SqlDataAdapter 的時候塞入的構造函數 SqlCommand,如下代碼:
然後繼續往下看 this.Fill 方法,代碼簡化後如下:
上面這段代碼沒啥好說的,繼續往下追蹤 <code>this.FillInternal</code> 方法,簡化後如下:
大家可以仔細研讀一下上面的代碼,挺有意思的,至少你可以擷取以下兩點資訊:
從各個 finally 中可以看到,當資料 fill 到 datatable 中之後,操作資料庫的幾大對象 <code>Connection,Transaction,DataReader</code> 都會進行關閉,你根本不需要操心。
<code>this.Fill(datatables, dataReader, startRecord, maxRecords)</code> 中可以看到,底層不出意外也是通過 <code>dataReader.read()</code> 一行一行讀取然後塞到 <code>DataTable</code>中去的,不然它拿這個 dataReader 幹嘛呢? 不信的話可以繼續往下追。
從上面代碼可以看到, dataReader 被封裝到了 DataReaderContainer 中,用 <code>FillNextResult</code> 判斷是否還有批語句sql,進而友善生成多個 datatable 對象,最後就是填充 DataTable ,當然就是用 <code>dataReader.Read()</code>啦,不信你可以一直往裡面追嘛,如下代碼:
到這裡你應該意識到: DataReader 的性能肯定比 Fill 到 DataTable 要高的太多,是以它和靈活性兩者之間看您取舍了哈。
剛才給大家介紹的是帶有 DataTable 參數的重載,其實除了這個還有另外四種重載方法,如下圖:
從字面意思看就是想從指定的位置 (startRecord) 開始讀,然後最多讀取 maxRecords 條記錄,很好了解,我們知道 <code>reader()</code> 是隻讀向前的,然後一起看一下源碼底層是怎麼實作的。

從上圖中可以看出,還是很簡單的哈,踢掉 startRecord 個 reader(),然後再隻讀向前擷取最多 maxRecords 條記錄。
這裡的 srcTable 是什麼意思呢? 從 vs 中看是這樣的: <code>The name of the source table to use for table mapping.</code> 乍一看也不是特别清楚,沒關系,我們直接看源碼就好啦,反正我也沒測試,嘿嘿。
從上圖中你應該明白大概意思就是給你 dataset 中的 datatable 取名字,比如:<code>name= 學生表</code>, 那麼database中的的 tablename依次是: <code>學生表,學生表1,學生表2 ...</code>, 這樣你就可以索引擷取表的名字了哈,如下代碼所示:
本篇就聊這麼多吧,算是解了多年之前我的一個好奇心,希望本篇對您有幫助。