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() 是只读向前的,然后一起看一下源码底层是怎么实现的。

从上图中可以看出,还是很简单的哈,踢掉 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["学生表"];
四:总结
本篇就聊这么多吧,算是解了多年之前我的一个好奇心,希望本篇对您有帮助。
以上就是c# sqldataadapter中的fill是怎么实现的的详细内容,更多关于c# sqldataadapter的资料请关注其它相关文章!
Fu3k丶you