21.4 快速数据访问:数据读取器
数据读取器(data reader)是从一个数据源中选择某些数据的最简单快捷的方法,但这也是功能最弱的一个方法。不能直接实例化数据阅读器,即实例是调用ExecuteReader()方法后从相应数据库的命令对象中返回的。
下面的代码说明了如何从Northwind数据库的Customer表中选择数据。这个示例连接了数据库,选择许多记录,循环所选的记录,并把它们输出到控制台上。
这个示例使用OLE DB提供程序作为一个来自SQL提供程序的简要的数据暂存器。在大多数情况下,OleDbClient类与SqlClient类是一对一的关系,例如OleDbConnection对象就类似于在前面的示例中所使用的SqlConnection对象。
要对OLE DB数据源执行命令,应使用OleDbCommand类。下面的代码执行一个简单的SQL语句,读取记录,返回一个OleDbDataReader对象。
注意下面的第二个using语句使OleDb类可用。
using System;
using System.Data.OleDb;
目前所利用的所有数据提供程序都在同一个DLL中,所以只需要引用System.Data.dll程序集,就可以导入本节使用的所有类:
public class DataReaderExample
{
public static void Main(string[] args)
{
string source = "Provider=SQLOLEDB;" +
"server=(local)\\NetSDK;" +
" integrated security=SSPI;" +
"database=northwind";
string select = "SELECT ContactName,CompanyName FROM Customers";
OleDbConnection conn = new OleDbConnection(source);
conn.Open();
OleDbCommand cmd = new OleDbCommand(select , conn);
OleDbDataReader aReader = cmd.ExecuteReader();
while(aReader.Read())
Console.WriteLine("'{0}' from {1}" ,
aReader.GetString(0) , aReader.GetString(1));
aReader.Close();
conn.Close();
}
}
前面的代码包含其他章节介绍的许多熟悉的C#功能。要编译该示例,使用下面的命令:
csc /t:exe /debug+ DataReaderExample.cs /r:System.Data.dll
在前面的示例中,下面的代码根据源连接字符串,创建一个新OLE DB.NET数据库连接:
OleDbConnection conn = new OleDbConnection(source);
conn.Open();
OleDbCommand cmd = new OleDbCommand(select, conn);
第三行根据特定的Select语句创建一个新OleDbCommand对象,以及执行命令时所使用的数据库连接。当有一个有效的命令时,就需要执行它,返回一个初始化了的OleDbDataReader:
OleDbDataReader aReader = cmd.ExecuteReader();
OleDbDataReader是一个只向前的连接游标,即只能沿着一个方向遍历记录,而使用的数据库连接一直打开,直到关闭DataReader为止。
提示:
OleDbDataReader会使数据库连接一直处于打开状态,直到显式关闭为止。
OleDbDataReader类不能直接实例化,它总是通过OleDbCommand类的ExecuteReader方法调用而返回。打开了一个数据读取器后,就可以用各种方式访问包含在该读取器中的数据。
关闭OleDbDataReader对象(显式调用Close()或通过垃圾收集器收集对象)时,底层的连接也会关闭。这取决于调用了哪个ExecuteReader()方法。如果调用了ExecuteReader()方法,并传递了CommandBehavior.CloseConnection,就会在关闭读取器的时候关闭连接。
OleDbDataReader类有一个索引器,可以使用常见的数组语法访问任何字段(但不是类型安全的访问):
object o = aReader[0];
object o = aReader["CategoryID"];
假定CategoryID字段是SELECT语句中用于生成阅读器的第一个字段,那么这两行语句的功能就是相同的,但后者比前者慢一些。为了验证这一点,编写一个简单的测试程序,从打开的数据读取器上对同一列进行一百万次的迭代访问,获取一些足够大的数字,虽然在一个循环中可能并不会对同一列访问一百万次,但按每秒来计算,就可能编写出最佳的代码。
另外,数字索引器平均每0.09秒就进行一百万次的访问,而文本索引器则需要0.63秒。原因是文本方法是从模式的内部查找列号,再使用列号的顺序进行访问。如果知道这个区别,就可以更好地访问数据。
是否应使用数字索引器?也许,但还有一种更好的方式。
除了上面给出的索引器外,OleDbDataReader还有一组类型安全的方法可以用于读取列,这些方法都可以进行自我解释,且都以Get开头。有一些方法可以读取大多数类型的数据,例如GetInt32、 GetFloat和GetGuid等。
前面使用GetInt32的一百万次迭代用了0.06秒,数字索引器中的系统开销是获取数据类型,调用与GetInt32相同的代码,然后装箱(本实例是拆箱)为一个整数。如果以前知道这种模式,并希望使用加密数字而不是列名,以避免对每个列访问使用类型安全的函数,这样运行速度就会比使用文本格式的列名快10倍(选择同一列的上百万个副本)。
在可维护性和速度之间有一个折中的问题。如果必须使用数字索引器,就应在类的范围内为每一个要访问的列定义常量。上面的代码可以用于从任何OLE DB数据库中选择数据,但有许多SQL Server的特定类可以使用,但其便利性有明显的损失。
下面的示例与上一示例基本相同,但在这个实例中用SQL提供程序和SQL类的引用替换了OLE DB提供程序和对OLE DB类的所有引用。代码的变化已经突出显示出来了。该示例在04_DataReaderSql目录下:
using System;
using System.Data.SqlClient;
public class DataReaderSql
{
public static int Main(string[] args)
{
string source ="server=(local)\\NetSDK;" +
" integrated security=SSPI;" +
"database=northwind";
string select = "SELECT ContactName,CompanyName FROM Customers";
SqlConnection conn = new SqlConnection(source);
conn.Open();
SqlCommand cmd = new SqlCommand(select , conn);
SqlDataReader aReader = cmd.ExecuteReader();
while(aReader.Read())
Console.WriteLine("'{0}' from {1}" , aReader.GetString(0) ,
aReader.GetString(1));
aReader.Close();
conn.Close();
return 0;
}
}
注意区别是什么?如果键入这些代码,用sql替换所有的OleDb,改变数据源字符串,重新编译。这是很容易的。
对Sql提供程序的索引器进行相同的性能测试,这次数字索引器也使用0.13秒就完成了百万次的访问,基于索引器的字符串运行了0.65秒,本机的Sql提供程序要比OleDb快,直到我在.NET版本中测试这一部分,情况都是这样。可以肯定这是不正常的,因为我使用的是最简单的测试(对同一个值选择1 000 000次),在托管SQL提供程序上进行真实的测试,会得到更好的性能。





