一个有趣的问题, 你知道SqlDataAdapter中的Fill是怎么实现的吗
一:背景
1. 講故事
最近因?yàn)楦鞣矫嬖驌Q了一份工作,去了一家主營物聯(lián)柜的公司,有意思的是物聯(lián)柜上的終端是用 wpf 寫的,代碼也算是年久失修,感覺技術(shù)債還是蠻重的,前幾天在調(diào)試一個(gè)bug的時(shí)候,看到了一段類似這樣的代碼:
var dt = new DataTable();SqlDataAdapter adapter = new SqlDataAdapter(new SqlCommand());adapter.Fill(dt);是不是很眼熟哈,或許你也已經(jīng)多年不見了,猶記得那時(shí)候?yàn)榱四軓臄?shù)據(jù)庫獲取數(shù)據(jù),第一種方法就是采用?SqlDataReader?一行一行從數(shù)據(jù)庫讀取,而且還要操心 Reader 的 close 問題,第二種方法為了避免麻煩,就直接使用了本篇說到的?SqlDataAdapter?,簡單粗暴,啥也不用操心,對了,不知道您是否和我一樣對這個(gè)?Fill?方法很好奇呢?,它是如何將數(shù)據(jù)塞入到?DataTable?中的呢?也是用的 SqlDataReader 嗎?而且?Fill?還有好幾個(gè)擴(kuò)展方法,哈哈,本篇就逐個(gè)聊一聊,就當(dāng)回顧經(jīng)典啦!
二:對Fill方法的探究
1. 使用 dnspy 查看Fill源碼
dnspy小工具大家可以到GitHub上面去下載一下,這里就不具體說啦,接下來追一下Fill的最上層實(shí)現(xiàn),如下代碼:
public int Fill(DataTable dataTable){IntPtr intPtr;Bid.ScopeEnter(out intPtr, "<comm.DbDataAdapter.Fill|API> %d#, dataTable\n", base.ObjectID);int result;try{DataTable[] dataTables = new DataTable[]{dataTable};IDbCommand selectCommand = this._IDbDataAdapter.SelectCommand;CommandBehavior fillCommandBehavior = this.FillCommandBehavior;result = this.Fill(dataTables, 0, 0, selectCommand, fillCommandBehavior);}finally{Bid.ScopeLeave(ref intPtr);}return result;}上面的代碼比較關(guān)鍵的一個(gè)地方就是?IDbCommand selectCommand = this._IDbDataAdapter.SelectCommand;?這里的 SelectCommand 來自于哪里呢?來自于你 new SqlDataAdapter 的時(shí)候塞入的構(gòu)造函數(shù) SqlCommand,如下代碼:
public SqlDataAdapter(SqlCommand selectCommand) : this(){this.SelectCommand = selectCommand;}然后繼續(xù)往下看 this.Fill 方法,代碼簡化后如下:
protected virtual int Fill(DataTable[] dataTables, int startRecord, int maxRecords, IDbCommand command, CommandBehavior behavior){result = this.FillInternal(null, dataTables, startRecord, maxRecords, null, command, behavior);return result;}上面這段代碼沒啥好說的,繼續(xù)往下追蹤?this.FillInternal?方法,簡化后如下:
private int FillInternal(DataSet dataset, DataTable[] datatables, int startRecord, int maxRecords, string srcTable, IDbCommand command, CommandBehavior behavior){int result = 0;try{IDbConnection connection = DbDataAdapter.GetConnection3(this, command, "Fill");try{IDataReader dataReader = null;try{dataReader = command.ExecuteReader(behavior);result = this.Fill(datatables, dataReader, startRecord, maxRecords);}finally{if (dataReader != null) dataReader.Dispose();}}finally{DbDataAdapter.QuietClose(connection, originalState);}}finally{if (flag){command.Transaction = null;command.Connection = null;}}return result;}大家可以仔細(xì)研讀一下上面的代碼,挺有意思的,至少你可以獲取以下兩點(diǎn)信息:
從各個(gè) finally 中可以看到,當(dāng)數(shù)據(jù) fill 到 datatable 中之后,操作數(shù)據(jù)庫的幾大對象?Connection,Transaction,DataReader?都會(huì)進(jìn)行關(guān)閉,你根本不需要操心。
this.Fill(datatables, dataReader, startRecord, maxRecords)?中可以看到,底層不出意外也是通過?dataReader.read()?一行一行讀取然后塞到?DataTable中去的,不然它拿這個(gè) dataReader 干嘛呢?不信的話可以繼續(xù)往下追。
從上面代碼可以看到, dataReader 被封裝到了 DataReaderContainer 中,用?FillNextResult?判斷是否還有批語句sql,從而方便生成多個(gè) datatable 對象,最后就是填充 DataTable ,當(dāng)然就是用?dataReader.Read()啦,不信你可以一直往里面追嘛,如下代碼:
private int FillLoadDataRow(SchemaMapping mapping){int num = 0;DataReaderContainer dataReader = mapping.DataReader;while (dataReader.Read()){mapping.LoadDataRow();num++;}return num;}到這里你應(yīng)該意識(shí)到:DataReader 的性能肯定比 Fill 到 DataTable 要高的太多,所以它和靈活性兩者之間看您取舍了哈。
二:Fill 的其他重載方法
剛才給大家介紹的是帶有 DataTable 參數(shù)的重載,其實(shí)除了這個(gè)還有另外四種重載方法,如下圖:
public override int Fill(DataSet dataSet);public int Fill(DataSet dataSet, string srcTable);public int Fill(DataSet dataSet, int startRecord, int maxRecords, string srcTable);public int Fill(int startRecord, int maxRecords, params DataTable[] dataTables);1. startRecord 和 maxRecords
從字面意思看就是想從指定的位置 (startRecord) 開始讀,然后最多讀取 maxRecords 條記錄,很好理解,我們知道?reader()?是只讀向前的,然后一起看一下源碼底層是怎么實(shí)現(xiàn)的。
從上圖中可以看出,還是很簡單的哈,踢掉 startRecord 個(gè) reader(),然后再只讀向前獲取最多 maxRecords 條記錄。
2. dataSet 和 srcTable
這里的 srcTable 是什么意思呢?從 vs 中看是這樣的:?The name of the source table to use for table mapping.?乍一看也不是特別清楚,沒關(guān)系,我們直接看源碼就好啦,反正我也沒測試,嘿嘿。
從上圖中你應(yīng)該明白大概意思就是給你 dataset 中的 datatable 取名字,比如:name= 學(xué)生表, 那么database中的的 tablename依次是:?學(xué)生表,學(xué)生表1,學(xué)生表2 ..., 這樣你就可以索引獲取表的名字了哈,如下代碼所示:
DataSet dataSet = new DataSet();dataSet.Tables.Add(new DataTable("學(xué)生表"));var tb = dataSet.Tables["學(xué)生表"];四:總結(jié)
本篇就聊這么多吧,算是解了多年之前我的一個(gè)好奇心,希望本篇對您有幫助。
總結(jié)
以上是生活随笔為你收集整理的一个有趣的问题, 你知道SqlDataAdapter中的Fill是怎么实现的吗的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员修神之路--简约而不简单的分布式通
- 下一篇: Hangfire定时触发作业,好像很简单