ado.net详细研究 —— DataReader
这次我们详细研究DataReader。我个人最喜欢的就是DataReader,虽然它不如DataSet强大,但是在很多情况下我们须要的是灵活的读取数据而不是大量的在内存里面缓存数据。比如在网络上每个用户都缓存大量的dataset,这很可能导致服务器内存不足。另外dataReader尤其适合读取大量的数据,因为它不在内存中缓存数据。
由于下面的讨论都设计到数据库操作,我们虚拟一个小项目:个人通讯录(单用户),这意味着我们须要一个contract的数据库,包含admin和fridend:
admin :Aname,Apassword
friend :Fname,Fphone,Faddress,Fid(主键)
当然,你可以根据自己的须要设计friend表,比如添加Fsex等字段,这里不详细列举。对应数据库建立文件:
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[admin]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [dbo].[admin] GO if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[friend]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [dbo].[friend] GO CREATE TABLE [dbo].[admin] ( [Aname] [varchar] (8) COLLATE Chinese_PRC_CI_AS NOT NULL , [Apassword] [varchar] (16) COLLATE Chinese_PRC_CI_AS NOT NULL ) ON [PRIMARY] GO CREATE TABLE [dbo].[friend] ( [Fid] [int] IDENTITY (1, 1) NOT NULL , [Fname] [varchar] (8) COLLATE Chinese_PRC_CI_AS NOT NULL , [Fphone] [varchar] (12) COLLATE Chinese_PRC_CI_AS NULL , [Faddress] [varchar] (100) COLLATE Chinese_PRC_CI_AS NULL ) ON [PRIMARY] GO
|
在讨论DataReader之前我们必须了解Connection和Command,虽然前面我们已经简短的介绍过了。
以下的所有讨论都针对Sql Server2000,使用的命名空间为System.Data.SqlClient。当然如果你须要使用OleDb,那是很方便的(基本上是把Sql替换成为OleDb就可以了)。
1, SqlConnection类
连接Sql server首先必须实例化一个SqlConnection对象:
SqlConnection Conn = new SqlConnection(ConnectionString);
Conn.Open();
或者
SqlConnection Conn = new SqlConnection();
Conn.ConnectionString = ConnectionString;
Conn.Open();
我比较喜欢前者,但是当你须要重新使用Connection对象去连接另外的数据库的时候,第二种方法非常有效(不过这种机会很少,一般来说一个小型系统只对应一个数据库——个人认为^_^)。
SqlConnection Conn = new SqlConnection();
Conn.ConnectionString = ConnectionString1;
Conn.Open();
//do something
Conn,Close();
Conn.ConnectionString = ConnectionString2;
Conn.Open();
//do something else
Conn,Close();
注意只有关闭一个连接以后才能使用另外的连接。
如果你不清楚Connection对象的状态,可以使用State属性,它的值为Open或者Closed,当然也还有其他值如Executing或者Broken,但是Sql server等当前版本都不支持。
If(Conn.State == ConnectionState.Open)
Conn.Colse();
上面一直提到ConnectionString,一般连接sql server的字符串为:
Data source = serverName;Initial catalog =contract;user id =sa;password= yourpassword;
如果你的sql server使用的是windows集成密码,则是:
Data source = serverName;Initial catalog = contract;Integrated Security=SSPI;
至于其他的oledb或者odbc连接串可以到http://www.connectionstrings.com
连接上数据库以后一定记得关闭连接,在ado.net中当Connection对象超出范围时连接不会自动关闭。
打开数据库连接以后我们要执行命令,所以我们讨论一下Command类
2 SqlCommand类
建立数据库连接以后我们须要访问和操作数据库——CRUD:Create、Read、Update、Delete。
为了执行命令我们创建Command对象,Comand对象要求执行Connection对象和CommandText对象。
SqlCommand cmd = new SqlCommand();
cmd.Connection = ConnectionObject;//比如我们先前建立的Conn对象
cmd.CommandText = CommandText;//比如一个Select语句
string CommandText = “Select * from friend”;
当然我们也可以使用存储过程,这个以后讨论。
另外的方法:
SqlCommand cmd = new SqlCommand(CommandText);
cmd.Connection = ConnectionObject;//比如我们先前建立的Conn对象
或者
SqlCommand cmd = new SqlCommand(CommandText,ConnecionObject);
另外还有一个包含三个参数的构造函数,我们不讨论。设计到的是事务处理。
有了Command对象以后我们须要执行操作,但是执行前请一定记得打开你的数据库连接,否则会有异常。SqlCommand对象提供下面4个执行方法:
l ExecuteNonQuery
l ExecuteScalar
l ExecuteReader
l ExecuteXmlReader
ExecuteNonQuery方法执行不返回结果的命令,通常使用它执行插入、删除、更新操作。
例如我们对contract数据库进行操作:
string sqlIns = “insert [friend] (Fname,Fphone,Faddress) values (‘雪冬寒’,’027-87345555’,’武汉大学宏博公寓’); cmd.CommandText = sqlIns; cmd.ExecuteNonQuery(); string sqlUpdate = “update [frined] set Faddress = ‘武汉大学’ where Fid = 1”; cmd.CommandText = sqlUpdate; cmd.ExecuteNonQuery(); string sqlDel = “delete from [friend] where Fid = 1; cmd.CommandText = sqlDel; cmd.ExecuteNonQuery();
|
注:如果你要测试以上代码,请自己书写,不要复制和粘贴,这样会存在文字错误(汉语和英语的符号问题)。
ExecuteScalar方法执行返回单个值的命令,例如我们须要统计系统中所有联系人的数量,就可以这样:
SqlCommand cmd = new SqlCommand(“select count(*) from friend”, Conn); Conn.open(); int friendCount = (int) cmd.ExecuteScalar(); MessageBox.Show(“共” + friendCount.ToString() + “个联系人”);
|
说明:命令可以返回多个结果,此时ExecuteScalar方法将返回第一行第一个字段的值,同时其他所有值不可访问,这意味着如果象获得最好的性能,您应该构造适当的select查询,以便查询的结果集中尽可能少的包含额外的数据。如果只对单个返回值感兴趣,这个方法是首选的方法。另外该方法返回object类型数据,所有保证进行适当的类型转换是您的责任,否则您将得到异常。
ExecuteXmlReader方法执行返回一个xml字符串的命令。它将返回一个包含所返回的sml的System.Xml.XmlReader对象。这个方法我一无所知,不作讨论^_^。
DataReader类
1. 创建DataReader对象
前面提到过没有构造函数创建DataReader对象。通常我们使用Command类的ExecuteRader方法来创建DataReader对象:
SqlCommand cmd = new SqlCommand(commandText,ConnectionObject) SqlDataReader dr = cmd.ExecuteReader();
|
DataReader类最常见的用法就是检索Sql查询或者存储过程返回的记录。它是连接的只向前和只读的结果集,也就是使用它时,数据库连接必须保持打开状态,另外只能从前往后遍历信息,不能中途停下修改数据。
注意:DataReader使用底层的连接,连接是它专有的,这意味这DataReader打开时不能使用对应连接进行去他操作,比如执行另外的命令等。使用完DataReader后一定记得关闭阅读器和连接。
2. 使用命令行为指定DataReader的特征
前面我们使用cmd.ExecuteReader()实例化DataReader对象,其实这个方法有重载版本,接受命令行参数,这些参数应该时Commandbehavior枚举:
SqlDataRader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
上面我们使用的是CommandBehavior.CloseConnection,作用是关闭DataReader的时候自动关闭对应的ConnectionObject。这样可以避免我们忘记关闭DataReader对象以后关闭Connection对象。别告诉我你不喜欢这个参数,你能保证你记得关闭连接。万一你忘记了呢?又或者你使用你的partner开发的组件来进行开发呢?这个组件并不一定让你有关闭连接的权限哦。另外CommandBehavior.SingleRow可以使结果集返回单个行,CommandBehavior.SingleResult返回结果为多个结果集的第一个结果集。当然Commandbehavior枚举还有其他值,请参见msdn。
3. 遍历DataReader中的记录
当ExecuteReader方法分会DataReader对象时,当前光标的位置时第一条记录的前面。必须调用数据阅读器的Read方法把光标移动到第一条记录,然后第一条记录就是当前记录。如果阅读器包含的记录不止一条,Read方法返回一个bool值true。也就是说Read方法的作用是在允许范围内移动光标位置到下一记录,有点类似rs.movenext,不是吗?如果当前光标指示着最后一条记录,此时调用Read方法得到false。我们经常这样做:
While(dr.Reader())
{
//do something with the current record
}
注意,如果你对每一条记录的操作可能花费比较长的时间,那么意味着阅读器将长时间打开,那么数据库连接也将维持长时间的打开状态。此时使用非连接的DataSet或许更好一些。
4. 访问字段的值
有2种方法。第一种是Item属性,此属性返回字段索引或者字段名字对应的字段的值。第二种是Get方法,此方法返回有字段索引指定的字段的值。有点难以理解,不是吗?不要紧,看例子就OK了。
Item属性
每个DataReader类都定义一个Item属性。比如现在我们有一个DataReader实例dr,对应的sql语句是select Fid,Fname from friend,则我们可以使用下面的方法取得返回的值:
object ID = dr[“Fid”]; object Name = dr[“Fname”]; 或者: object ID = dr[0]; object Name = dr[0];
|
注意索引总是从0开始的。另外也许您发现了我们使用的是object来定义对ID和Name,是的,Item属性返回的值是object型,但是您可以强制类型转换。
int ID = (int)dr[“Fid”];
string Name = (string)dr[“Fname”];
记住:确保类型转换的有效性是您自己的责任,否则您将得到异常。
Get方法
起始我们在第一篇文章里面已经使用过改方法了。每个DataReader都定义了一组Get方法,比如GetInt32方法把返回的字段值作为.net clr 32位证书。同上面的例子一样我们用如下方式访问Fid和Fname的值:
int ID = dr.GetInt32(0); string Name = dr.GetString(1);
|
注意虽然这些方法把数据从数据源类型转化为.net数据类型,但是他们不执行其他的数据转换,比如他们不会把16位整数转换为32位的。所以您必须使用正确的Get方法。另外Get方法不能使用字段名来访问字段,也就是说上面的没有:
int ID = dr.GetInt32(“Fid”); //错误
string Name = dr.GetString(“Fname”); //错误
显然上面这个缺点在某些场合是致命的,当你的字段很多的时候,或者说你过了一段时间以后再来看你这些代码,你会觉得很难以理解!当然我们可以使用其他方法来尽量解决这个问题。一个可行的办法是使用const:
const int FidIndex = 0;
const int NameIndex = 1;
int ID = dr.GetInt32(FidIndex);
string Name = dr.GetString(NameIndex);
这个办法并不怎么好,另外一个好一些的办法:
int NameIndex = dr.GetOrdinal(“Fname”); //取得Fname对应的索引值
string Name = dr.GetString(NameIndex);
这样似乎有点麻烦,但是当须要遍历阅读器种大量的结果集的时候,这个方法很有效,因为索引只需执行一次。
int FidIndex = dr.GetOrdinal(“Fid”);
int NameIndex = dr.GetOrdinal(“Fname”);
while(dr.Read())
{
int ID = dr.GetInt32(FidIndex);
string Name = dr.GetInt32(NameIndex);
}
到目前为止,我们已经讨论了DataReader的基本操作了