c# SqlDataAdapter中的Fill是怎么实现的

作者:一线码农 时间:2021-07-16 08:30:04 

1. 讲故事

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


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

是不是很眼熟哈,或许你也已经多年不见了,犹记得那时候为了能从数据库获取数据,第一种方法就是采用 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;
}

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


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

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

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

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

  • 从各个 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;
}

从上面代码可以看到, 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;
}

到这里你应该意识到: 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. startRecord 和 maxRecords

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

c# SqlDataAdapter中的Fill是怎么实现的

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

2. dataSet 和 srcTable

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

c# SqlDataAdapter中的Fill是怎么实现的

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


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

四:总结

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

来源:https://www.cnblogs.com/huangxincheng/p/13358901.html

标签:c#,SqlDataAdapter,fill,实现
0
投稿

猜你喜欢

  • error LNK2019: 无法解析的外部符号 问题的解决办法

    2022-07-27 23:02:40
  • C#获取指定PDF文件页数的方法

    2021-10-10 17:15:43
  • 使用flutter的showModalBottomSheet遇到的坑及解决

    2021-12-07 22:58:29
  • C#程序调用cmd.exe执行命令

    2022-11-14 04:04:50
  • Android仿QQ微信侧滑删除效果

    2023-07-08 11:22:15
  • c# Parallel类的使用

    2022-09-09 12:30:21
  • Java工具类之@RequestMapping注解

    2023-11-16 03:00:54
  • Android中GPS坐标转换为高德地图坐标详解

    2023-10-07 20:40:24
  • Spring整合Quartz实现动态定时器的示例代码

    2022-10-22 06:48:17
  • Android 全屏无标题栏的三种实现方法

    2022-01-05 03:01:31
  • 遍历Hashtable 的几种方法

    2023-07-18 13:49:11
  • SpringCloud Zuul网关功能实现解析

    2022-09-12 14:30:31
  • 在 Java 中将Object 转换为 Int的四种方法

    2023-05-05 21:37:30
  • Android实现Activity、Service与Broadcaster三大组件之间互相调用的方法详解

    2023-04-09 08:51:30
  • java selenium 操作弹出窗口示例代码

    2021-06-27 05:29:08
  • C#微信开发之接收 / 返回文本消息

    2021-08-21 14:37:11
  • C++11中std::packaged_task的使用详解

    2022-03-12 20:08:23
  • Spring session 获取当前账户登录数的实例代码

    2022-10-17 10:02:05
  • C#开发Winform实现文件操作案例

    2022-04-28 15:30:53
  • 详解Java的Spring框架下bean的自动装载方式

    2022-10-23 08:13:59
  • asp之家 软件编程 m.aspxhome.com