天天看点

你知道SqlDataAdapter中的Fill是怎么实现的吗

最近因为各方面原因换了一份工作,去了一家主营物联柜的公司,有意思的是物联柜上的终端是用 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> 是只读向前的,然后一起看一下源码底层是怎么实现的。

你知道SqlDataAdapter中的Fill是怎么实现的吗

从上图中可以看出,还是很简单的哈,踢掉 startRecord 个 reader(),然后再只读向前获取最多 maxRecords 条记录。

这里的 srcTable 是什么意思呢? 从 vs 中看是这样的: <code>The name of the source table to use for table mapping.</code> 乍一看也不是特别清楚,没关系,我们直接看源码就好啦,反正我也没测试,嘿嘿。

你知道SqlDataAdapter中的Fill是怎么实现的吗

从上图中你应该明白大概意思就是给你 dataset 中的 datatable 取名字,比如:<code>name= 学生表</code>, 那么database中的的 tablename依次是: <code>学生表,学生表1,学生表2 ...</code>, 这样你就可以索引获取表的名字了哈,如下代码所示:

本篇就聊这么多吧,算是解了多年之前我的一个好奇心,希望本篇对您有帮助。