搜索

一个有趣的问题,你知道SqlDataAdapter中的Fill是怎么实现的吗

发表于 2025-11-05 05:33:44 来源:全栈开发

一、问题背景

讲故事

最近因为各方面原因换了一份工作,知道中去了一家主营物联柜的实现公司,有意思的问题是物联柜上的终端是用 wpf 写的,代码也算是知道中年久失修,感觉技术债还是实现蛮重的,前几天在调试一个bug的问题时候,看到了一段类似这样的知道中代码:

复制var dt = new DataTable(); SqlDataAdapter adapter = new SqlDataAdapter(new SqlCommand()); adapter.Fill(dt);1.2.3.

是不是很眼熟哈,或许你也已经多年不见了,实现犹记得那时候为了能从数据库获取数据,问题第一种方法就是知道中采用 SqlDataReader 一行一行从数据库读取,而且还要操心 Reader 的实现 close 问题,第二种方法为了避免麻烦,问题就直接使用了本篇说到的 SqlDataAdapter ,简单粗暴,知道中啥也不用操心,实现对了,不知道您是否和我一样对这个 Fill 方法很好奇呢?,香港云服务器它是如何将数据塞入到 DataTable 中的呢?也是用的 SqlDataReader 吗?而且 Fill 还有好几个扩展方法,哈哈,本篇就逐个聊一聊,就当回顾经典啦!

二、对Fill方法的探究

1. 使用 dnspy 查看Fill源码

dnspy小工具大家可以到GitHub上面去下载一下,这里就不具体说啦,接下来追一下Fill的最上层实现,如下代码:

复制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; }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.

上面的代码比较关键的一个地方就是 IDbCommand selectCommand = this._IDbDataAdapter.SelectCommand; 这里的 SelectCommand 来自于哪里呢?来自于你 new SqlDataAdapter 的时候塞入的构造函数 SqlCommand,如下代码:

复制public SqlDataAdapter(SqlCommand selectCommand) : this() { this.SelectCommand = selectCommand; }1.2.3.4.

然后继续往下看 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; }1.2.3.4.5.6.

上面这段代码没啥好说的,继续往下追踪 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; }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.

大家可以仔细研读一下上面的代码,挺有意思的,至少你可以获取以下两点信息:

从各个 finally 中可以看到,当数据 fill 到 datatable 中之后,操作数据库的几大对象 Connection,Transaction,DataReader 都会进行关闭,站群服务器你根本不需要操心。this.Fill(datatables, dataReader, startRecord, maxRecords) 中可以看到,底层不出意外也是通过 dataReader.read() 一行一行读取然后塞到 DataTable中去的,不然它拿这个 dataReader 干嘛呢?不信的话可以继续往下追。 复制protected virtual int Fill(DataTable[] dataTables, IDataReader dataReader, int startRecord, int maxRecords) { try { int num = 0; bool flag = false; DataSet dataSet = dataTables[0].DataSet; int num2 = 0; while (num2 < dataTables.Length && !dataReader.IsClosed) { DataReaderContainer dataReaderContainer = DataReaderContainer.Create(dataReader, this.ReturnProviderSpecificTypes); if (num2 == 0) { bool flag2; do { flag2 = this.FillNextResult(dataReaderContainer); } while (flag2 && dataReaderContainer.FieldCount <= 0); } } result = num; } return result; }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.

从上面代码可以看到, dataReader 被封装到了 DataReaderContainer 中,用 FillNextResult 判断是否还有批语句sql,从而方便生成多个 datatable 对象,最后就是填充 DataTable ,当然就是用 dataReader.Read()啦,不信你可以一直往里面追嘛,如下代码:

复制private int FillLoadDataRow(SchemaMapping mapping) { int num = 0; DataReaderContainer dataReader = mapping.DataReader; while (dataReader.Read()) { mapping.LoadDataRow(); num++; } return num; }1.2.3.4.5.6.7.8.9.10.11.12.

到这里你应该意识到:DataReader 的性能肯定比 Fill 到 DataTable 要高的太多,所以它和灵活性两者之间看您取舍了哈。

三、Fill 的其他重载方法

刚才给大家介绍的是带有 DataTable 参数的重载,其实除了这个还有另外四种重载方法,如下图:

复制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.2.3.4.

1. startRecord 和 maxRecords

从字面意思看就是想从指定的位置 (startRecord) 开始读,然后最多读取 maxRecords 条记录,很好理解,云服务器提供商我们知道 reader() 是只读向前的,然后一起看一下源码底层是怎么实现的。

图片

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

2. dataSet 和 srcTable

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

图片

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

复制DataSet dataSet = new DataSet(); dataSet.Tables.Add(new DataTable("学生表")); var tb = dataSet.Tables["学生表"];1.2.3.

四、总结

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

随机为您推荐
版权声明:本站资源均来自互联网,如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

Copyright © 2016 Powered by 一个有趣的问题,你知道SqlDataAdapter中的Fill是怎么实现的吗,全栈开发  滇ICP备2023006006号-32sitemap

回顶部