ADO.NET详细研究(五)--DataReader终结篇
2024-07-10 13:05:19
供稿:网友
中国最大的web开发资源网站及技术社区,
这一次我们将把datareader了结,同时我们提到的有些技巧与datareader无关但是是很基本的也很有用的技巧。
一,参数化查询
在上一篇文章发表以后不少网友提意见说代码不规范,没有对sql使用参数,这确实是很大一个漏洞,所以我在这里首先谈一下参数化查询问题。
使用参数化查询的好处:可以防止sql注入式攻击,提高程序执行效率。
针对sql server .net data provider,我们可以使用@作为前缀标记的参数。比如:
const string connstr = "data source=bineon;user=sa;password=test;initial catalog=northwind;";
string sql = "select productid,productname from products";
sql += " where categoryid = @categoryid and productid < @categoryid ";
sqlconnection conn = new sqlconnection(connstr);
sqlcommand cmd = new sqlcommand(sql,conn);
cmd.parameters.add("@categoryid",categoryidvalue);
cmd.parameters.add("@maxproductid",maxproductidvalue);
conn.open();
sqldatareader reader = cmd.executereader();
上面的代码段在定义sql语句的时候使用了两个参数@categoryid和@categoryid。为了是参数在执行过程中获得具体值,我们使用prarmeter对象,通过它把参数添加到command对象上,这样就获得参数化查询。
当然上面使用的add方法有其他重载版本,比如我们可以自己定义parameter对象然后再添加:
sqlparameter para = new sqlparameter("@categoryid",categoryidvalue);
cmd.parameters.add(para);
上面sqlparameter的构造函数也有多个重载版本。具体可以查看msdn。
注意:上面的参数必须使用@前缀,另外也不仅仅是查询才能使用参数,其他更新数据库的操作类似的都能采用参数。
上面我们给出了针对sql server参数化查询的方法,现在我们讨论在oledb 和odbc中指定参数。
其实这两种provider都不支持指定参数的方法,但是我们可以在查询中使用(?)作为占位符,去指定参数将出现的位置。
sql = "select productid,productname from products";
sql += " where categoryid =? and productid < ?";
接下来我们同样应该把parameter对象添加到command的parameters集合里面,但是这个时候注意参数添加的顺序必须和你使用?的顺序相通,这个是与上面sql server .net data provider不同的地方。
oledbcommand cmd = new oledbcommand(sql,conn);
cmd.parameters.add(“catid”,categoryidvalue);
cmd.parameters.add(“maxproductid”,maxproductidvalue);
如果上面添加参数的次序弄反了,那么maxproductidvalue将被指定到第一个?那里,那么就出错了。另外上面的参数名catid和maxproductid无所谓,你怎么命名都可以,甚至是空串也行。
注意:上面参数名无所谓,但是添加参数的次序很重要,不能颠倒。同样的其他更新数据库的操作也支持(?)占位符。
二,使用输出参数检索数据
这种方法的前提是使用存储过程。其实对支持存储过程的dbms比如sql server来说,其上的所有操作都应该使用存储过程,以获得更好的执行效率。
比如我现在需要在我的联系人数据库中找出一个和指定id相同的联系人的姓名(关于联系人数据库请看我的上一篇文章),我应该怎么做呢?一个办法是使用datareader,但是效率如何?另外也许我们可以选择更好的executescalar(),但是如果我想知道的是联系人的姓名和电话呢?executescalar()的效率确实比datareader好,但是它只能返回单个值,这个时候它也不能满足要求。我们这里使用存储过程输出参数来解决这个问题。存储过程如下:
create procedure getinfo
(
@fid int,
@fname varchar(8) output,
@fphone varchar(12) output
)
as
select @fname = fname,@fphone = fphone
from friend
where fid = @fid
go
上面的关键字output指明参数是输出参数。
然后我们编写代码:
sqlconnection conn = new sqlconnection(connstr);
sqlcommand cmd = conn.createcommand();
cmd.commandtext = "getinfo";
cmd.commandtype = commandtype.storedprocedure;
上面的代码新建conn对象和cmd对象,并把cmd对象的执行命令指定为名为getinfo的存储过程。接下来我们需要给cmd对象的parameters集合添加parameter对象了。
sqlparameter param = cmd.parameters.add("@fid",16);
param = cmd.parameters.add("@fname",sqldbtype.varchar,8);
param.direction = parameterdirection.output;
param = cmd.parameters.add("@fphone",sqldbtype.varchar,8);
param.direction = parameterdirection.output;
我们注意到了上面的@fname和@fphone参数添加的时候指定了参数为输出方向,这个就是和存储过程里面的参数方向是一致的。下面我们执行命令同时获得对应的值。
conn.open();
cmd.executenonquery();
string fname = cmd.parameters["@fname"].value.tostring();
string fphone = cmd.parameters["@fphone"].value.tostring();
conn.close();
三,检索多个无关的结果集
有时候我们需要对不同的表(也可能是相同的表,但是查询内容不同)进行无关的查询,比如我想查看所有联系人的姓名,然后在查看所有联系人的住址。当然这个需要我们完全可以一个sql语句搞定,也不需要所谓的多个记录集,但是请允许我以这个需求来演示多个记录集的操作。
多个查询语句之间使用;分开。具体代码如下:
sqlconnection conn = new sqlconnection(connstr);
sqlcommand cmd = conn.createcommand();
string sqla = "select fname from friend";
string sqlb = "select fphone from friend";
cmd.commandtext = sqla + ";" + sqlb;
然后我们可以和以往一样获得datareader,但是由于是多个记录集,我们读取完第一个记录集以后如果使用下一个记录集呢?答案是nextresult()方法,该方法为bool类型,如果有下一个记录集就返回真,否则为假。
conn.open();
sqldatareader reader= cmd.executereader();
int i = 1;
do
{
console.writeline("第" + i.tostring() + "个记录集内容如下:/n");
while(reader.read())
{
console.writeline(reader[0].tostring() + "/t");
}
i++;
}while(reader.nextresult());
注意:由于datareader本身向前只读的特性,您不能比较多个记录集的内容,也不能在多个记录集中来回移动。结果为多个结果集时,数据读取器定位在第一个记录集上,这个和数据读取器的read()方法不同(该方法默认在记录集之前)。
另外这个例子仅仅时演示多个无关记录集的使用方法,所以请不要追究例子的实际意义。另外当我们需要检索相关的记录信息时,多表连接查询也是一个很好的选择,也就是使用sql join查询。
四,其他相关技术
检索二进制数据
检索二进制数据的时候我们必须把commandbehavior.sequentialaccess枚举值传递给executereader方法。另外就是要注意从 记录集读取信息的时候必须按照你的sql语句的顺序读取。比如:
sql = “select pub_id,pr_info,logo from pub_info where pub_id=’0763’ “;
那么你读取的时候必须先取得pub_id,然后才是pr_info,再接着时logo,如果你读取了pr_info以后又想读取pub_info,这时你将得到异常。另外需要注意的是读取大量二进制数据的时候比较好的办法是使用getbytes方法。下面的代码演示如果读取logo的二进制数据。
system.io.memorystream stream = new system.io.memorystream();
system.io.binarywriter writer = new system.io.binarywriter(stream);
int buffersize = 1024;
byte[] buffer = new byte[buffersize];
long offset = 0;
long bytesread = 0;
do
{
bytesread = reader.getbytes(2,offset,buffer,0,buffersize);
writer.writer(buffer,0,(int)bytesread);
writer.flush();
offset += bytesread;
}
while(bytesread == buffersize);
其中getbytes方法参数比较多,具体请参见msdn:
ms-help://ms.msdnqtr.2003feb.2052/cpref/html/frlrfsystemdatasqlclientsqldatareaderclassgetbytestopic.htm
检索模式信息
如果我们只想取得数据库表的模式信息,怎么办?datareader的getschematable方法可以满足我们的要求。
string sql = "select fid,fname,fphone from friend";
cmd.commandtext = sql;
conn.open();
sqldatareader reader = cmd.executereader();
datatable schematable = reader.getschematable();
然后我们可以遍历datatable获得所有模式信息
datarowcollection schemacolumns = schematable.rows;
datacolumncollection schemaprops = schematable.columns;
foreach(datarow schemacolumn in schemacolumns)
{
foreach(datacolumn schemacolumnprop in schemaprops)
{
console.writeline(schemacolumnprop.columnname + "=" + schemacolumn[schemacolumnprop.columnname].tostring());
}
}
但是上面的效率不高,因为我们不需要读取数据集,但是程序实际上做了这个工作。一个可行的解决办法是把commandbehavior.schemaonly枚举传递给executerader方法,另外的办法就是构造特殊的sql命令,比如:
sql = “select fid,fname,fphone from friend where fid = 0”
datareader的功能基本上就介绍完了,如果需要更详细的资料请查看msdn。下一次我们将讨论dataset的简单功能。