C#与数据库访问
C#与数据库访问技术
ADO.NET(ActiveX Data Object.NET)是Microsoft公司开发的用于数据库连接的一套组件模型,是ADO的升级版本。
由于ADO.NET组件模型很好地融入了.NET Framework,所以拥有.NET Framework的平台无关、高效等特性。程序员能使用ADO.NET组件模型,方便高效地连接和访问数据库。
1 ADO.NET概述
ADO.NET是与数据库访问操作有关的对象模型的集合,它基于Microsoft的.NET Framework,在很大程度上封装了数据库访问和数据操作的动作。
ADO.NET同其前身ADO系列访问数据库的组件相比,做了以下两点重要改进:
ADO.NET引入了离线的数据结果集(Disconnected DataSet)这个概念,通过使用离线的数据结果集,程序员更可以在数据库断开的情况下访问数据库。
ADO.NET还提供了对XML格式文档的支持,所以通过ADO.NET组件可以方便地在异构环境的项目间读取和交换数据。
1.1 ADO.NET体系结构
ADO.NET组件的表现形式是.NET的类库,它拥有两个核心组件:.NET Data Provider(数据提供者)和DataSet(数据结果集)对象。
.NET Data Provider是专门为数据处理以及快速地只进、只读访问数据而设计的组件,包括Connection、Command、DataReader和DataAdapter四大类对象,其主要功能是:
在应用程序里连接数据源,连接SQL Server数据库服务器。
通过SQL语句的形式执行数据库操作,并能以多种形式把查询到的结果集填充到DataSet里。
DataSet对象是支持ADO.NET的断开式、分布式数据方案的核心对象。DataSet是数据的内存驻留表示形式,无论数据源是什么,它都会提供一致的关系编程模型。它是专门为独立于任何数据源的数据访问而设计的。
DataSet对象的主要功能是:
用其中的DataTable和DataRelations对象来容纳.NET Data Provider对象传递过来的数据库访问结果集,以便应用程序访问。
(2)把应用代码里的业务执行结果更新到数据库中。
并且,DataSet对象能在离线的情况下管理存储数据,这在海量数据访问控制的场合是非常有利的。
图2-1描述了ADO.NET组件的体系结构。
图2-1 ADO.NET体系结构
1.2 ADO.NET对象模型
ADO.NET对象模型中有5个主要的数据库访问和操作对象,分别是Connection(连接)、Command(控制)、DataReader、DataAdapter(数据修改)和DataSet对象。
其中,Connection对象主要负责连接数据库,Command对象主要负责生成并执行SQL语句,DataReader对象主要负责读取数据库中的数据,DataAdapter对象主要负责在Command对象执行完SQL语句后生成并填充DataSet和DataTable,而DataSet对象主要负责存取和更新数据。
ADO.NET主要提供了两种数据提供者(Data Provider),分别是SQL Server.NET Provider和OLE DB.NET Provider(提供者)。
SQL Server.NET Framework数据提供程序使用它自身的协议与SQL Server数据库服务器通信,而OLEDB.NET Framework则通过OLE DB服务组件(提供连接池和事务服务)和数据源的OLE DB提供程序与OLE DB数据源进行通信。
它们两者内部均有Connection、Command、DataReader和DataAdapter 4类对象。对于不同的数据提供者,上述4种对象的类名是不同的,而它们连接访问数据库的过程却大同小异。
这是因为它们以接口的形式,封装了不同数据库的连接访问动作。正是由于这两种数据提供者使用数据库访问驱动程序屏蔽了底层数据库的差异,所以从用户的角度来看,它们的差别仅仅体现在命名上。
表2-1描述了这两类数据提供者下的对象命名。
表2-1 ADO.NET对象描述
对象名 |
OLE DB数据提供者的类名 |
SQL Server数据提供者类名 |
Connection对象 |
OleDbConnection |
SqlConnection |
Command对象 |
OleDbCommand |
SqlCommand |
DataReader对象 |
OleDbDataReader |
SqlDataReader |
DataAdapter对象 |
OleDbDataAdapter |
SqlDataAdapter |
2.2 Connection 对象与数据库连接
在不同的Provider类型下,Connection对象的命名也是不同的,但它们有一个共同的功能,那就是管理与数据源的连接。
2.2.1 Connection对象的常用属性
Connectionion对象主要用于连接数据库,它的常用的属性如下。
Ü ConnectionString属性:该属性用来获取或设置用于打开SQL Server
数据库的字符串。
ÜConnectionTimeout属性:该属性用来获取在尝试建立连接时终止尝试,
并生成错误之前所等待的时间。
ÜDataBase属性:该属性用来获取当前数据库或连接打开后要使用的数据库
的名称。
ÜDataSource属性:该属性用来设置要连接的数据源实例名称,
例如SQLServer的Local服务实例。
Ü State性该属性:是一个枚举类型的值,用来表示同当前数据库的
连接状态。该属性的取值情况和含义如表2-2所示。
表2-2 Provider值描述(ConnectionSate枚举成员值)
属 性 值 |
对应含义 |
Broken |
该连接对象与数据源的连接处于中断状态。只有当连接打开后再与数据库失去连接才会导致这种情况。可以关闭处于这种状态的连接,然后重新打开(该值是为此产品的未来版本保留的) |
Closed |
该连接处于关闭状态 |
Connecting |
该连接对象正在与数据源连接(该值是为此产品的未来版本保留的) |
Executing |
该连接对象正在执行数据库操作的命令 |
Fetching |
该连接对象正在检索数据 |
Open |
该连接处于打开状态 |
State属性一般是只读不写的,以下代码演示了使用State属性管理控制数据连接的方式。
//设置连接对象
SqlConnection conn;
//如果是空闲状态,连接数据库
if(conn.State == ConnectionState.Closed)
{
conn.Open();
}
//访问数据库的代码
……
//最后关闭连接
if(conn.State == ConnectionState.Open)
{
conn.Close();
}
2.2.2 Connection对象的连接字符串
在ConnectionString连接字符串里,一般需要指定将要连接数据源的种类、
数据库服务器的名称、数据库名称、登录用户名、密码、等待连接时间、
安全验证设置等参数信息,这些参数之间用分号隔开。下面将详细描述
这些常用参数的使用方法。
1. Provider参数
Provider参数用来指定要连接数据源的种类。如果使用的是SQL Server
Datahovider,则不需要指定Provider参数,因为SQL Server DataProvider已经
指定了所要连接的数据源是SQl Server服务器。如果使用的是O1eDB Data
Provider或其他连接数据库,则必须指定Provider参数。表2-3说明了Provider参数值和连接数据源类型之间的关系。
表2-3 Provider值描述
Provider值 |
对应连接的数据源 |
SQL OLE DB |
Microsoft OLEDB Provider for SQL Server |
MSDASQL |
Microsoft OLEDB Provider for ODBC |
Microsoft. Jet. OLEDB.4.0 |
Microsoft OLEDB Provider for Access |
MSDAORA |
Microsoft OLEDB Provider for Oracle |
2.Server参数
Server参数用来指定需要连接的数据库服务器(或数据域)。比如
Server=(local),指定连接的数据库服务器是在本地。如果本地的数据库
还定义了实例名,Server参数可以写成Server=(local)\实例名。
另外,可以使用计算机名作为服务器的值。
如果连接的是远端的数据库服务器,Server参数可以写成Server=IP或
“Server=远程计算机名”的形式。
Server参数也可以写成Data Source,比如Data Source=IP。
server=(local);Initial Catalog=student;user Id=sa; password= ;
Data Source=(localhost);Initial Catalog=student;user Id=sa; password= ;
3.DataBase参数
DataBase参数用来指定连接的数据库名。比如DataBase=Master,
说明连接的数据库是Master,DataBase参数也可以写成
Initial Catalog,如Initial Catalog=Master。
4.Uid参数和Pwd参数
Uid参数用来指定登录数据源的用户名,也可以写成UserID。
比如Uid(User ID)=sa,说明登录用户名是sa。
Pwd参数用来指定连接数据源的密码,也可以写成Password。
比如Pwd(Password)=asp.net,说明登录密码是asp.net。
5.Connect Timeout参数
Connect Timeout参数用于指定打开数据库时的最大等待时间,单位是秒。
如果不设置此参数,默认是15秒。如果设置成-1,表示无限期等待,一般不推荐使用。
6.Integrated Security参数
Integrated Security参数用来说明登录到数据源时是否使用SQL Server
的集成安全验证。如果该参数的取值是True(或SSPI,或Yes),
表示登录到SQL Server时使用Windows验证模式,即不需要通过Uid和Pwd这样的方式登录。如果取值是False(或No),表示登录SQL Server时使用Uid和Pwd方式登录。
一般来说,使用集成安全验证的登录方式比较安全,因为这种方式不会暴露用户名和密码。
安装SQL Server时,如果选中“Windows身份验证模式”单选按钮则应该使用如下的连接字符串
Data Source=(local); Init Catalog=students; Integrated Security=SSPI;
Integrated Security=SSPI表示连接时使用的验证模式是
Windows身份验证模式。
7.Pooling、MaxPool Size和Min Pool Size参数
Pooling参数用来说明在连接到数据源时,是否使用连接池,默认是True。当该值为True时,系统将从适当的池中提取SQLConnection对象,或在需要时创建该对象并将其添加到适当的池中。当取值为False时,不使用连接池。
当应用程序连接到数据源或创建连接对象时,系统不仅要开销一定的通信和内存资源,还必须完成诸如建立物理通道(例如套接字或命名管道),与服务器进行初次握手,分析连接字符串信息,由服务器对连接进行身份验证,运行检查以便在当前事务中登记等任务,因此往往成为最为耗时的操作。
实际上,大多数应用程序仅使用一个或几个不同的连接配置。这意味着在执行应用程序期间,许多相同的连接将反复地打开和关闭。为了使打开的连接成本最低,ADO.NET使用称为Pooling(即连接池)的优化方法。
在连接池中,为了提高数据库的连接效率,根据实际情况,预先存放了若干数据库连接对象,这些对象即使在用完后也不会被释放。应用程序不是向数据源申请连接对象,而是向连接池申请数据库的连接对象。另外,连接池中的连接对象数量必须同实际需求相符,空置和满载都对数据库的连接效率不利。
Max Pool Size和Min Pool Size这两个参数分别表示连接池中最大和最小连接数量,默认分别是100和0。根据实际应用适当地取值将提高数据库的连接效率。
8.综合实例
下面通过实例来说明连接字符串的具体含义。
如果连接字符串是:
"Provider= Microsoft.Jet.OleDB.4.0;Data Source=D:\login.mdb"
则说明数据源的种类是Microsoft.Jet.OleDB.4.0,数据源是D盘下的login.mdb Access数据库,用户名和密码均无。
如果连接字符串是:
"Server= (local); DataBase=Master;Uid =sa;Pwd=;ConnectionTimeout=20"
由于没有指定Provider,所以可以看出该连接字符串用于创建SqlConnection对象,连接SQL Server数据库。需连接的SQL Server数据库服务器是local,数据库是Master,用户名是sa,密码为空,而最大连接等待时间是20秒。
2.2.3 Connection对象的常用方法
Connection类型的对象用来连接数据源。在不同的数据提供者的内部,Connection对象的名称是不同的,在SQL Server Data Provider里叫SqlConnection,而在OLE DB Data Provider里叫OleDbConnection。
下面将详细介绍Connection类型对象的常用方法。
1.构造函数
构造函数用来构造Connection类型的对象。对于SqlConnection类,其构造函数说明如表2-4所示。
表2-4 SqlConnection类构造函数说明
函数定义 |
参数说明 |
函数说明 |
SqlConnection() |
不带参数 |
创建SqlConnection对象 |
SqlConnection(string connectionstring) |
连接字符串 |
根据连接字符串,创建SqlConnection对象 |
第1种:
String ConnectionString =”server=(local); Initial Catalog =stu; ”;
SqlConnection conn=new SqlConnection();
conn.ConnectionString=ConnectionString;
conn.Open();
第2种
String cnn=”server=(local); Initial Catalog =stu; ”;
SqlConnection conn=new SqlConnection(cnn);
conn.Open();
显然使用第2种方法输入的代码要少一点,但是两种方法执行的效率并
没有什么不同,另外,如果需要重用Connection对象去使用不同的身份连
接不同的数据库时,使用第一种方法则非常有效。例如:
SqlConnection conn=new SqlConnection();
conn.ConnectionString=connectionString1;
conn.Open();
//访问数据库,做一些事情
conn.Close();
conn.ConnectionString=connectionString2;
conn.Open();
//访问数据库,做另外一些事情
conn.Close();
注意:只有当一个连接关闭以后才能把另一个不同的连接字符串赋值给
Connection 对象。如果不知道Connection对象在某个时候是打开是
关闭时,可以检查Connection对象的State属性,它的值可以是Open,
也可以是Closed,这样就可以知道连接是否是打开的。
表2-5说明了OleDbConnection类的构造函数。可以看出,它们和SqlConnection类的构造函数非常相近。
表2-5 OleDbConnection类构造函数说明
函数定义 |
参数说明 |
函数说明 |
OleDbConnection() |
不带参数 |
创建OleDbConnection对象 |
OleDbConnection(string connectionstring) |
连接字符串 |
根据连接字符串,创建OleDbConnection对象 |
2.Open和Close方法
Open和Close方法分别用来打开和关闭数据库连接,都不带参数,
均无返回值。
Open方法:使用ConnectionString所指定的属性设置打开数据库连接
Close方法:关闭与数据库的连接,这是关闭任何打开连接的首选方法
3.SqlCommand(OleDbCommand)CreateCommand()方法
SqlCommand(OleDbCommand)CreateCommand()方法用来创建一个Command类型的对象。
Command类对象一般用来执行SQL语句,关于Command对象的操作将在2.3节里详细描述。
CreateCommand()方法:创建并返回一个与SqlConnection关联的SqlCommand对象
ChangeDatabase方法:为打开的SqlConnection更改当前数据库。
注意:数据库连接是很有价值的资源,因为连接要使用到宝贵的系统资源,如内存和网络带宽,因此对数据库的连接必须小心使用,要在最晚的时候建立连接(调用Open方法),在最早的时候关闭连接(调用Close方法)。也就是说在开发应用程序时,不再需要数据连接时应该立刻关闭数据连接。这点看起来很简单,要达到这个目标也不难,关键是要有这种意识。
2.2.4 Connection对象连接数据源代码示例
以下代码演示使用连接字符串创建数据库连接的一般方式。
//连接Access数据库
string connStr="Provider= Microsoft.Jet.OleDB.4.0;Data Source=D:\login.mdb"
//根据字符串创建OleDbConnection连接对象
OleDbConnection objConnection=new OleDbConnection(strConnect);
//打开数据源连接
if(objConnection.State==ConnectionState.Closed)
{
objConnection.Open();
}
//使用结束后关闭数据源连接
if(objConnection.State==ConnectionState.Open)
{
objConnection.Close();
}
在这段代码里的业务逻辑是:
(1)创建连接字符串,从中可以看出Connection对象是使用OleDB类型的Data Provider,连接到D盘下login.mdb的Access数据库中。
(2)根据连接字符串,创建Connection类型的对象,这里用到了OleDbConnection。
(3)打开数据源的连接。
(4)执行数据库的访问操作代码。
(5)关闭数据源连接。
完整案例
1、利用SQL Server2000建立一个数据库Student,并建立相应的表studentInfo
2、用Visual C#2005建立一个基于Window的应用程序,并添加一个按钮,如图下
3、双击按钮,自动切换到后台代码编辑文件Form1.cs中,并自动添加了与此按钮的Click事件相关的处理程序button1_Click(object sender, EventArgs e)
4、在Form1.cs文件中添加如下命名空间:
using System.Data.SqlClient;
5、在事件处理程序button1_Click中添加代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.SqlClient;
namespace DataBase
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
try
{
SqlConnection conn = new SqlConnection();
//conn.ConnectionString = "server=(local);user id=sa;
Initial Catalog=Student;pwd=;";
conn.ConnectionString = "server=(local);Initial Catalog=Student;
Integrated Security=SSPI;";
conn.Open();
if (conn.State == ConnectionState.Open)
{
MessageBox.Show("连接已经打开");
}
conn.Close();
if (conn.State == ConnectionState.Closed)
{
MessageBox.Show("连接已经关闭");
}
}
catch (Exception ex)
{
MessageBox.Show("数据库连接失败" + ex.Message);
}
}
}
}
2.3 Command(数据命令)对象与查询语句
建立了数据库连接之后,就可以执行数据访问操作和数据操纵操作了。一般对数据库的操作被概括为CRUD——Create、Read、Update和Delete。ADO.NET中定义了Command类去执行这些操作。
和Connection对象类似,在.NET中存在SqlCommand和OleDbCommand,除了OleDbCommand类没有ExecuteXmlReader方法之外,OleDbCommand与SqlCommand非常类似。
Command对象主要用来执行SQL语句。利用Command对象,可以查询数据和修改数据。
Command对象是由Connection对象创建的,其连接的数据源也将由Connection来管理。而使用Command对象的SQL属性获得的数据对象,将由DataReader和DataAdapter对象填充到DataSet里,从而完成对数据库数据操作的工作。
2.3.1 Command对象的常用属性
Command对象的常用属性有Connection、ConnectionString、CommandType、CommandText和CommandTimeout。
1.Connection属性
Connection属性用来获得或设置该Command对象的连接数据源。比如某SqlConnection类型的conn对象连在SQL Server服务器上,又有一个Command类型的对象cmd,可以通过cmd.Connection=conn来让cmd在conn对象所指定的数据库上操作。
不过,通常的做法是直接通过Connection对象来创建Command对象,而Command对象不宜通过设置Connection属性来更换数据库,所以上述做法并不推荐。
2.ConnectionString属性
ConnectionString属性用来获得或设置连接数据库时用到的连接字符串,用法和上述Connection属性相同。同样,不推荐使用该属性来更换数据库。
3.CommandType属性
CommandType属性用来获得或设置CommandText属性中的语句是SQL语句、数据表名还是存储过程。该属性的取值有3个:
如果把CommandType设置成为Text或不设置,说明CommandText属性的值是一个SQL语句。
如果把CommandType设置成为TableDirect,说明CommandText属性的值是一个要操作的数据表的名。(SQL Server.NET数据提供程序不支持该属性值)
如果把CommandType设置成为StoredProcedure,说明CommandText属性的值是一个存储过程。
如果不显示设置CommandType的值,则CommandType默认为Text。
表 CommandType枚举值
值 |
说明 |
StoredProcedure |
指示CommandType属性的值为存储过程的名称 |
TableDirect |
指示CommandType属性的值为一个或多个表的名称 只有OLE DB的.NET Framework数据提供程序才支持TableDirect |
Text |
指示CommandType属性的值为SQL文本命令(默认) |
String cnstr="Sever=(local); database=student; Integrated Security=true";
SqlConnection conn=new SqlConnection(cnstr);
conn.Open();
SqlCommand cmd=new SqlCommand("select * from Student", conn);
4.CommandText属性
根据CommandType属性的不同取值,可以使用CommandText属性获取或设置SQL语句、数据表名(仅限于OLE DB数据库提供程序)或存储过程。
2.3.2 Command对象的常用方法
同样,在不同的数据提供者的内部,Command对象的名称是不同的,在SQL Server Data Provider里叫SqlCommand,而在OLE DB Data Provider里叫OleDbCommand。
下面将详细介绍Command类型对象的常用方法,包括构造函数、执行不带返回结果集的SQL语句方法、执行带返回结果集的SQL语句方法和使用查询结果填充DataReader对象的方法。
1.构造函数
构造函数用来构造Command对象。对于SqlCommand类型的对象,其构造函数说明如表 2-6所示。
表2-6 SqlCommand类构造函数说明
函数定义 |
参数说明 |
函数说明 |
SqlCommand() |
不带参数 |
创建SqlCommand对象 |
SqlCommand(string cmdText) |
cmdText: SQL 语句字符串 |
根据SQL语句字符串,创建SqlCommand对象 |
SqlCommand(string cmdText, SqlConnection connection) |
cmdText: SQL 语句字符串 connection: 连接到的数据源 |
根据数据源和SQL语句,创建SqlCommand对象 |
SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction) |
cmdText: SQL语句字符串 connection: 连接到的数据源 transaction: 事务对象 |
根据数据源和SQL语句和事务对象,创建SqlCommand对象 |
(1) 第一个构造函数不带任何参数
SqlCommand cmd=newe SqlCommand();
cmd.Connection=ConnectionObject;
cmd.CommandText=CommandText;
上面代码段使用默认的构造函数创建一个SqlCommand对象。然后,把已有的Connection对象ConnectionObject和命名文本CommandText分别赋给了Command对象的Connection属性和CommandText属性。
例如,CommandText可以从数据库检索数据的SQL select语句:
string CommandText=" select *from studentInfo ";
除此之外,许多关系型数据库,例如SQL Server 和Oracle,都支持存储过程。可以把存储过程的名称指定为命名文本。例如,使用编写 GetAllStudent存储过程为命名文本:
string CommandText=" GetAllStudent ";
cmd.CommandType=CommandType.StoredProcedure;
(2) 第二个构造函数可以接受一个命令文本
SqlCommand cmd=newe SqlCommand(CommandText);
cmd.Connection=ConnectionObject;
上面的代码实例化了一个Command对象,并使用给定命令文本对Command对象的CommandText属性进行了初始化。然后,使用已有的Connection 对象对Command对象的Connection属性进行了赋值。
(3) 第三个构造函数接受一个Connection和一个命名文本
SqlCommand cmd=newe SqlCommand(CommandText, ConnectionObject);
注意这两个参数的顺序,第一个为string类型的命令文本,第二个为Connection对象。
(4) 第四个构造函数接受三个参数,第三个参数是SqlTransaction对象,这里不做讨论。
另外,Connection 对象提供了CreateCommand方法,该方法将实例化一个Command对象,并将其Connection属性赋值为建立该Command对象的Connection对象。
无论在什么情况下,当把Connection对象赋值给Command对象的Connection属性时,并不需要Connection对象是打开的。但是,如果连接没有打开,则在命令执行之前必须首先打开连接。
而对于OleDbCommand类型的对象,其构造函数如2-7所示。同样可以看出,它们和SqlCommand类的构造函数非常相似。
表2-7 OleDbCommand类构造函数说明
函数定义 |
参数说明 |
函数说明 |
OleDbCommand() |
不带参数 |
创建OleDbCommand对象 |
OleDbCommand(string cmdText) |
cmdText: SQL语句字符串 |
根据SQL语句字符串,创建OleDbCommand对象 |
OleDbCommand(string cmdText,OleDbConnection connection) |
cmdText: SQL语句字符串 connection:连接到的数据源 |
根据数据源和SQL语句,创建OleDbCommand对象 |
OleDbCommand(stringcmdText, OleDbConnection connection , OleDbTransaction transaction) |
cmdText: SQL语句字符串 connection:连接到的数据源 transaction:事务对象 |
根据数据源和SQL语句和事务对象,创建OleDbCommand对象 |
命令对象构造完成后,就可以执行命令对数据库进行操作了。命令对象所提供的用于执行命令的方法有很多种,具体使用哪个方法取决于命令的执行结果返回什么样的数据。
SqlCommand 提供了4个执行方法:ExecuteNonQuery()、ExecuteScalar()、ExecuteReader()、ExecuteXmlReader()。详细见下面相关部分。
命令对象提供的用于执行命令的方法及其含义如表
方法 |
含义 |
Cancel |
试图取消命令的执行 |
ExecuteNonQuery |
对连接执行SQL语句并返回受影响的行数 |
ExecuteReader |
执行查询,将查询结果返回到数据读取器(DataReader)中 |
ExecuteScalar |
执行查询,并返回查询所返回的结果集中第一行的第一列。忽略额外的列或行 |
ExecuteXmlReader |
执行查询,将查询结果返回到一个XmlReader对象中 |
2.ExecuteNonQUery方法
ExecuteNonQuery方法用来执行Insert、Update、Delete等非查询语句和其他没有返回结果集的SQL语句,并返回执行命令后影响的行数。如果Update和Delete命令所对应的目标记录不存在,返回0。如果出错,返回-1。
String cnstr="server=(local);database=student; Integrated Security=true";
SqlConnection cn=new SqlConnection(cnstr);
cn.Open();
string sqlstr="update student set name='Jone' where name='Bill' ";
SqlCommand cmd=new SqlCommand(sqlstr, cn);
cmd.ExecuteNonQuery();
cn.Close();
ExecuteNonQuery()方法的返回值是一个整数,代表操作所影响到的行数。
3.ExecuteScalar()方法
在许多情况下,需要从SQL语句返回一个结果,例如客户表中记录的个数,当前数据库服务器的时间等。ExecuteScalar()方法就适用于这种情况。
ExecuteScalar方法执行一个SQL命令,并返回结果集中的首行首列(执行返回单个值的命令)。如果结果集大于一行一列,则忽略其他部分。根据该特性,这个方法通常用来执行包含Count、Sum等聚合函数的SQL语句。
下面的代码读取数据库中表student的记录个数,并把它输出到控制台上。
String cnstr="server=(local);database=student; Integrated Security=true";
SqlConnection cn=new SqlConnection(cnstr);
cn.Open();
string sqlstr="select count(*) from student";
SqlCommand cmd=new SqlCommand(sqlstr, cn);
object count=cmd.ExecuteScalar();
Console.WriteLine(count.ToString());
cn.Close();
ExecuteScalar()方法的返回值类型是Object,根据具体需要,可以将它转换为合适的类型。
4.ExecuteReader()方法
ExecuteReader()方法执行命令,并使用结果集填充DataReader对象。
ExecuteReader()方法用于执行查询操作,它返回一个DataReader对象,通过该对象可以读取查询所得的数据。
ExecuteReader()方法在Command对象中用得比较多,通过DataReader类型的对象,应用程序能够获得执行SQL查询语句后的结果集。该方法的两种定义为:
ExecuteReader(),不带参数,直接返回一个DataReader结果集。
ExecuteReader(CommandBehavior behavior),根据behavior的取值类型,决定DataReader的类型。
如果behavior取值是CommandBehavior.SingleRow这个枚举值,则说明返回的ExecuteReader只获得结果集中的第一条数据。如果取值是CommandBehavior.SingleResult,则说明只返回在查询结果中多个结果集里的第一个。
一般来说,应用代码可以随机访问返回的ExecuteReader列,但如果behavior取值为 CommandBehavior.SequentialAccess,则说明对于返回的ExecuteReader对象只能顺序读取它包含的列。也就是说,一旦读过该对象中的列,就再也不能返回去阅读了。这种操作是以方便性为代码换取读数据时的高效率,需谨慎使用。
String cnstr="server=(local);database=student; Integrated Security=true";
SqlConnection cn=new SqlConnection(cnstr);
cn.Open();
string sqlstr="select * from student";
SqlCommand cmd=new SqlCommand(sqlstr, cn);
SqlDataReader dr=cmd.ExecuteReader();
while(dr.Read())
{
String name=dr["姓名"].ToString();
Console.WriteLine(name);
}
dr.Close();
cn.Close();
这段代码从数据库的student表中读取全部数据,并把该表的“姓名”字段的数据全部输出到控制台上。
ExecuteXmlReader
SqlCommand特有的方法,OleDbCommand无此方法。该方法执行将返回XML字符串的命令。它将返回一个包含所返回的XML的System.Xml.XmlReader对象。
2.3.3 Command对象创建SQl语句代码示例
在下面这段代码里,首先根据连接字符串创建一个SqlConnecdon连接对象,并用此对象连接数据源:然后创建一个SqlCommand对象,并用此对象的ExecuteNonQuery方法执行不带返回结果集的SQL语句。
//连接字符串
private static string strConnect=" data source=localhost;
uid=sa;pwd=aspent;database=LOGINDB"
// 根据连接字符串创建SqlConnection 连接句柄
SqlConnetion objConnection =new SqlConnection(strConnect);
//数据库命令
SqlCommand objCommand =new SqlCommand( " ",objConnection);
// 设置sql语句
objCommand.CommandText= " INSERT INTO USERS " + " (USERNAME, NICKNAME, USERPASSWORD, USEREMAIL, USERROLE, CREATDATE, LASTMODIFYDATE) "+ " VALUES " +" (@USERNAME, @NICKNAME, @USERPASSWORD, @USEREMAIL, @USERROLE, @CREATDATE, @LASTMODIFYDATE ) ";
// 以下省略设置各值的语句
……
try
{
//打开数据库连接
if( objConnection.State == ConnectionState. Closed )
{
objConnection.Open();
}
//获取运行结果,插入数据
objCommand.ExecuteNonQuery();
//省略后继动作
……
}
catch(SqlException e)
{
Response.Write(e.Message.ToString());
}
finally
{
//关闭数据库连接
if(objConnection.State == ConnectionState.Open)
{
objConnection.Close();
}
}
这段代码是连接数据库并执行操作的典型代码。其中,操作数据库的代码均在try… catch … finally结构中,因此代码不仅能正常地操作数据库,更能在发生异常的情况下抛出异常。另外,不论是否发生异常,也不论发生了哪种数据库操作的异常,finally块里的代码均会被执行,所以,一定能保证代码在访问数据库后关闭连接。
而在下面的代码里,将使用Command对象执行查询类的SQL语句,并将结果集赋给DataRead对象。
private static string strConnect=" data source=localhost;
uid=sa;pwd=aspent;database=LOGINDB"
SqlConnetion objConnection =new SqlConnection(strConnect);
SqlCommand objCommand =new SqlCommand( " ",objConnection);
// 设置sql语句
objCommand.CommandText= "SELECT * FROM USERS ";
try
{
//打开数据库连接
if( objConnection.State == ConnectionState. Closed )
objConnection.Open();
//获取运行结果
SqlDataReader result=objCommand.ExecuteReader();
//省略后继动作
……
}
catch(SqlException e)
{
Response.Write(e.Message.ToString());
}
finally
{
//关闭数据库连接
if(objConnection.State == ConnectionState.Open)
{
objConnection.Close();
}
}
这里用到DataReader对象来获得结果集,如果仅仅想返回查询结果集的第一行第一列的值,可以将SqlDataReader result=objCommand.ExecuteReader();改成objCommand.ExecuteScalar().ToString();
综合示例
表:
1、ExecuteScalar方法
ExecuteScalar方法执行返回单个值的命令。例如,如果想获取Student数据库中表studentInfo的学生的总人数,则可以使用这个方法执行SQL查询:
Select count(*) from studentInfo .
(1) 建立Windows Application 应用程序
(2) 在Form1上添加一个按钮Button控件和一个标Label签控件
(3) 双击按钮,自动进入代码编辑界面
首先添加命名空间: using System.Data.SqlClient;
(4)编写按钮的Click事件的处理事件代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.SqlClient;
namespace DataBase
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
try
{
//定义命令文本
string commandText = "select count(*) from studentInfo";
//定义连接字符串
string connString="server=(local);Initial Catalog=Student;
Integrated Security=SSPI;";
//string connString= "server=(local);user id=sa;
Initial Catalog=Student;pwd=;";
//定义Connection对象
SqlConnection conn = new SqlConnection();
//设置Connection对象的ConnectionString属性
conn.ConnectionString = connString;
//新建Command对象,此时conn对象并不需要打开连接
SqlCommand cmd = new SqlCommand(commandText, conn);
//打开连接
conn.Open();
//执行命令,返回结果
string count = cmd.ExecuteScalar().ToString();
//记得关闭连接
conn.Close();
this.label1.Text = "共有" + count + "位学生!";
}
catch (Exception ex)
{
MessageBox.Show("数据库连接失败" + ex.Message);
}
}
}
}
执行结果界面如图:
分析代码:
第1步是引入命名空间:System.Data.SqlClient,表示将使用SQL Server.NET 数据提供程序: using System.Data.SqlClient;
第2步是 按钮button1_Click单击事件中首先新建立了连接并设置了其连接字符串属性:
string connString="server=(local);Initial Catalog=Student;Integrated Security=SSPI;";
//string connString= "server=(local);user id=sa;Initial Catalog=Student;pwd=;";
//定义Connection对象
SqlConnection conn = new SqlConnection();
//设置Connection对象的ConnectionString属性
conn.ConnectionString = connString;
第三步,新建Command 对象,并将命名文本和连接对象传递给其构造函数:
SqlCommand cmd = new SqlCommand(commandText, conn);
其中,commandText为最初定义的命名文本:
string commandText = "select count(*) from studentInfo";
此时conn对象没有打开,因为这不是必须的。
第四步 现在需要执行操作了,所以首先要打开连接,然后执行操作:
conn.Open();
string count = cmd.ExecuteScalar().ToString();
由于ExecuteScalar()方法返回类型为object,因此使用了ToString()方法将其转换为string以后赋值给count。
注意:一般使用ExecuteScalar()方法时都必须用到类型转换。
一般使用ExecuteScalar方法时都必须用到类型转换。
第五步数据库访问完毕以后应该立即关闭连接,这是一个好习惯:
corm.Close();
第六步最后将读取的信息通过label1显示出来:
this.label1.Text="共有"+count+"位学生!";
上面的代码中并没有指定Command对象的CommandType属性,这时CommandType的值将为默认的Text,当然也可以通过如下代码显示指定其类型,但这不是必须的。
cmd.CommandType=CommandType.Text;
2.ExecuteNonQuery方法
ExecuteNonQuery方法主要用来更新数据。通常使用它来执行Update、Insert和Delete语句。该方法返回值意义如下:对于Update、Insert和Delete语句,返回值为该命令所影响的行数。对于所有其他类型的语句,返回值为-1。
Command对象通过ExecuteNonQuery方法更新数据库的过程非常简单,需要进行的步骤如下:
(1)创建数据库连接。
(2)创建Command对象,并指定一个SQL Insert、Update、Delete查询或存储过程。
(3)把Command对象依附到数据库连接上。
(4)调用ExecuteNonQuery方法。
(5)关闭连接。
下面依次看一看更新、添加和删除操作。
更新记录
下面的代码显示了一个简单的数据库更新操作,其作用是修改学号为“20013150
的学生信息:
string updateQuery="Update studentInfo set sName='小李'"+"Where ID='200131500145'";
//新建连接
SqlConnection conn=new SqlConnection();
conn.Connectionstring=connectionString;
//新建命令对象
SqlCommand cmd=new SqlCommand(updateQuery,conn);
// 调用命令对象的ExecuteNonQuery方法
conn.Open();
int RecordsAffected=cmd. ExecuteNonQuery();
conn.Close();
代码本身非常简单。但需要注意的是ExecuteNonQuery方法的返回值,这个方法返回命令影响的记录数量。例如,如果命令是SQL UPDATE语句,则将返回被更新记录的数量。相似的,当执行INSERT命令时返回插入到数据库的记录的数量。如果期望命令更新记录,但是ExecuteNonQuery方法返回的值为0,则说明更新操作失败了。
(1)字符串拼接方式
也许读者已经注意到了在上面的代码中updateQuery是在程序中定义的,其操作固定在了程序中,用户无法和应用程序交互,而在实际中命令应该根据用户输入的信息进行处理,比如用户在文本框中输入了新的用户信息以后单击更新,然后程序将用户输入的数据更新到数据库。要达到这个目的,就必须依据用户输入的数据来构造命令。构造命令可以有多种形式。假设现在已经将用户输入的数据保存到了变量中:
String userName="小李";
String userId="200131500145";
此时userName和user id变量分别保存了学生姓名和学号,命名可以通过如下拼接命令字符串的形式构造:
string updateQuery="Update student Set sName=' "+username+" ' "+
"Where ID=' "+user id+" ' "
这种拼接字符串构造命令的方式是最直接最简单的,但也是最不安全的。可以采用参数化来实现相同的功能。
(2)参数化方式
SQL Server.NET数据提供程序和OLE DB.NET数据提供程序在指定参数时区别非常大,下面分别介绍。
①在SQL Server .NET数据提供程序中指定参数
SQL Server .NET数据提供程序支持指定的参数。当命令文本在指定具体命令时,必须指出哪一部分是在运行时进行设置的,也就是必须指出哪部分是参数。那些可变的部分即参数,它们都必须有一个@前缀。
Update student set sName=@userName where ID=@user id
这个命令中,@userName和@user id为参数,它们的值在运行时是可变的。当命令中带参数时,构造Command对象的方法和前面的并没有任何不同:
string updateQuery="Update student Set sName=@username+"+"Where ID=@user id+" ;
SqlConnection conn=new SqlConnection(connectionString);
SqlCommand cmd=new SqlCommand(updateQuery, conn);
现在就有了包含参数的Command对象。目前需要做的就是为命令中的每一个参数创建一个Parameter对象。SqlCommand类提供了一个Parameters集合属性,用以为命令保存所有的参数。通过调用Parameters集合的Add方法,在集合中添加一个新的参数。
crud. Parameters.Add (" @userName", userName);
cmd. Parameters.Add("@user id", user id);
上面Add方法中的第一个参数为命令中的参数名,后面的userName则是用于定义的变量,保存了用户输入的信息。除此之外,可以用其他方法创建Parameter对象,然后添加到集合中。
SqlParameter paramUserName= new SqlParameter("@userName",SqlDbType.NVarChar,50);
paramUserName.Value=userName;
cmd. Parameters.Add(paramUserName) ;
上面的代码首先新建了一个SqlParameter对象,命名为paramUserName,该对象对应于命令中的@userName参数,在SqlParameter的构造函数中为参数指定了类型为SqlDbType.NVarChar,长度为50。接着为paramUserName指定了Value属性,表示在运行时将用这个值代替命令中的@userName。最后是调用Add方法将参数添加到命令的参数集合中,这一步很容易被初学者忽略,要格外注意。
带参数的命令设置好以后可以和往常一样执行ExecuteNonQuery方法,这并没有任何不同。
除了直接使用SQL语句作为命令以外,还可以使用存储过程作为命令内容。为了ADO.NET应用程序中执行存储过程,需要把存储过程的名称赋给命令文本,同时将命令的CommandType属性设置为存储过程。如果存储过程返回值,或者有一些参数,还必须创建参数,并把创建的参数添加到命令的Parameters集合中。
在数据库Student添加如下名为UpdateStudentInfo的存储过程,代码如下:
CREATE PROCEDURE UpdateStudentInfo
(
@userName nvarchar(20),
@user id nvarchar(20);
)
AS
Update studentInfo
Set sName=@userName Where ID=@user id
GO
为了执行该存储过程,必须创建一个Command对象并将存储过程的名称传入它的构造函数。
SqlConnection conn=new SqlConnection(connectionString);
SqlCommand cmd=new SqlCommand("UpdateStudentInfo", conn);
接下来要把命令的CommandType属性设置为StoredProcedure。
cmd.CommandType=CommandType.StoredProcedure;
后续步骤和参数化命令是相同的,先设置参数然后执行对应命令。
实例:更新记录
在本例子中,建立一个供用户输入学生学号和姓名的文本框和几个对应不同操作类型的更新信息按钮,当用户输入信息以后单击相应的按钮则执行相应的操作。在此实例中还将接触到服务器信息验证的相关知识。
(1)新建名为UpdateTest的Windows Application应用程序,在默认的Forml.cs中添加2个Label控件,2个TextBox控件,3个Button控件,按表4.7所示设置这7个控件的属性。
表4.7控件属性
控件类型 ID属性 Text属性 |
标签 lblUserID 学号: 标签 lblUserName 姓名: 文本框 txtUserlD 文本框 txtUserName 按钮 btnExecute1 拼接字符串 按钮 btnExecute2 使用参数 按钮 btnExecute3 使用存储过程 |
(2)调整控件的位置,如图4.16所示。
图4.16 设置控件的位置
(3)双击“拼接字符串”按钮,注册按钮btnExecute的按钮单击事件btnExecute1_Click,然后再切换到Form1.cs页面的“设计”视图,依次双击“使用参数”和“使用存储过程”按钮来注册对应的按钮单击事件btnExecute2_Click和btnExecute3_Click。
(4)在Form1.cs文件中首先引入命名空间System.Data.SqlClient,然后添加一个名 CheckInfo的方法,返回值为bool类型,代码如下:
bool CheckInfo()
{
//判断学号是否输入
if (this.txtUserID.Text.Trim() == "")
{
Alert("学号不完整");
return false;
}
else if (this.txtUserName.Text.Trim() == "") //判断姓名是否输入
{
Alert("姓名不完整");
return false;
}
//信息检查通过
return true;
}
其中,Alert是自定义的另外一个方法,用来弹出一个对话框,定义如下:
///<summary>
///弹出提示信息对话框
///</summary>
///<param name="message">要提示的信息</param>
void Alert(string message)
{
MessageBox.Show(null, message, "信息提示",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
在btnExecute1_Click中编写如下代码:
private void btnExecute1_Click(object sender, EventArgs e)
{
//信息检查
if(this.CheckInfo())
{
//取值
string userId=this.txtUserID.Text.Trim();
string userName=this.txtUserName.Text.Trim();
//新建连接对象
SqlConnection conn=new SqlConnection();
conn.ConnectionString="Data Source=(local);Initial Catalog=Student;
Integrated Security=SSPI";
//拼接命令字符串
string updateQuery="update StudentInfo set sName='"+userName+"'"+
"where ID='"+userId+"’";
//新建命令对象
SqlCommand cmd=new SqlCommand(updateQuery,conn);
conn.Open();
//保存执行结果
int RecordsAffected=cmd.ExecuteNonQuery();
conn.Close();
//提示结果
Alert("更新数据数为"+RecordsAffected);
}
}
在btnExecute2_Click中编写如下代码:
private void btnExecute2_Click(object sender, EventArgs e)
{
//信息检查
if(this.CheckInfo())
{
//取值
string userId=this.txtUserID.Text.Trim();
string userName=this.txtUserName.Text.Trim();
//新建连接对象
SqlConnection conn=new SqlConnection();
conn.ConnectionString="Data Source=(local);
Initial Catalog=Student;Integrated Security=SSPI";
//拼接命令字符串
string updateQuery="update StudentInfo set sName=@userName where ID=@userId";
//新建命令对象
SqlCommand cmd=new SqlCommand(updateQuery,conn);
//添加参数
cmd.Parameters.Add(new SqlParameter("@userName", userName));
cmd.Parameters.Add(new SqlParameter("@userId", userId));
conn.Open();
//保存执行结果
int RecordsAffected = cmd.ExecuteNonQuery();
conn.Close();
/*
try
{
conn.Open();
//保存执行结果
int RecordsAffected = cmd.ExecuteNonQuery();
}
catch (Exception err)
{
MessageBox.Show(err.Message, "修改记录失败");
}
finally
{
if (conn.State == ConnectionState.Open)
{
conn.Close();
}
}*/
//提示结果
Alert("更新数据数为"+RecordsAffected);
}
}
在btnExecute3_Click中编写如下代码:
private void btnExecute3_Click(object sender, EventArgs e)
{
//信息检查
if (this.CheckInfo())
{
//取值
string userId = this.txtUserID.Text.Trim();
string userName = this.txtUserName.Text.Trim();
//新建连接对象
SqlConnection conn = new SqlConnection();
conn.ConnectionString = "Data Source=(local);
Initial Catalog=Student;Integrated Security=SSPI";
//新建命令对象
SqlCommand cmd = new SqlCommand("UpdateStudentInfo", conn);
//指定命令类型为存储过程
cmd.CommandType = CommandType.StoredProcedure;
//添加参数
cmd.Parameters.Add(new SqlParameter("@userName", userName));
cmd.Parameters.Add(new SqlParameter("@userId", userId));
conn.Open();
//保存执行结果
int RecordsAffected = cmd.ExecuteNonQuery();
conn.Close();
//提示结果
Alert("更新数据数为" + RecordsAffected);
}
}
(9)在学号和姓名中分别输入信息以后,单击任意按钮即可测试更新结果。例如分别
输入学号"2007102001"和姓名“某某”后单击任意按钮,可以得到如图4.18所示界面。
图4.18 测试更新数据
代码讲解
在引入了System.Data.SqlClient命名空间以后,使用了SQL Server .NET数据提供程序对数据进行更新。
更新数据前使用了CheckInfo方法对数据进行检查,查看用户是否在姓名和学号两个文本框中输入了有效的信息,如果两者的输入信息都有效,则该方法返回true,否则返回false,方法实现如下:
//判断学号是否输入
if (this.txtUserID.Text.Trim() == "")
{
Alert("学号不完整");
return false;
}
else if (this.txtUserName.Text.Trim() == "") //判断姓名是否输入
{
Alert("姓名不完整");
return false;
}
//信息检查通过
return true;
当用户输入的信息不正确时,Checklnfo将调用Alert方法显示提示信息对话框,Alert方法实际上是固定MessageBox.Show方法的一些参数,利用其来弹出对话框,Alert方法实现非常简单,仅仅需要下面一句代码:
MessageBox.Show(null,message,"信息提示",MessageBoxButtons.OK,MessageBoxIcon.Information);
其中,message是Alert方法接受的参数。
在3个按钮单击事件中,程序代码分别实现对应的数据更新操作,这些操作前面都进行了详细的讲解,这里不再赘述。
添加记录
从程序员的角度看,向数据库中添加新的记录与更改现有记录没有任何区别。为SQL INSERT命令或者存储过程创建一个Command对象(如果需要的话,把参数添加6
Command对象),然后执行它。例如,如果要在student表中添加一个新的学生记录,以使用下面的代码示例:
private void btnAdd_Click(object sender, EventArgs e)
{
string connectionString = "Data Source=(local);Initial Catalog=Student;
Integrated Security=SSPI";
//拼接命令字符串
string insertQuery = "Insert studentInfo(ID,sName,sGrade,sSex,sEmail,sPhone,sAddress)"
+ "values('2007001001','小张','2007106','男','test@test.com'," +
"'18888888000','成都金牛区')";
//新建连接
SqlConnection conn = new SqlConnection(connectionString);
//新建命令对象
SqlCommand cmd = new SqlCommand(insertQuery, conn);
conn.Open();
//保存执行结果
int RecordsAffected = cmd.ExecuteNonQuery();
conn.Close();
}
如果数据库中对应字段为标识,则不需要通过程序手段来添加数据,事实上如果这样则将造成操作失败。例如向课程表中添加新的信息时则不需要也不允许向ID中添加数据。ID中的数据由数据库系统自动维护。
删除记录
删除记录与使用命令更新数据非常类似。下面是一个删除学生记录的示例代码:
private void btnDel_Click(object sender, EventArgs e)
{
string connectionString = "Data Source=(local);Initial Catalog=Student;
Integrated Security=SSPI";
//拼接命令字符串
string deletetQuery = "Delete from studentInfo where ID='2007001001'";
//新建连接
SqlConnection conn = new SqlConnection(connectionString);
//新建命令对象
SqlCommand cmd = new SqlCommand(deletetQuery, conn);
conn.Open();
//保存执行结果
int RecordsAffected = cmd.ExecuteNonQuery();
conn.Close();
}
4.4数据阅读器
当执行返回结果集的命令时,需要一个方法从结果集中提取数据。处理结果集的方法有两个:第一,使用数据阅读器(DataReader):第二,同时使用数据适配器(Data Adapter)和ADO.NET数据集(DataSet)。本节将学习数据阅读器的有关知识。
4.4.1 DataReader类
在ADO.NET中由每个数据提供程序实现自己的DataReader。
数据读取器(DataReader)是从一个数据源中选择某些数据的最简单的方法,但也是功能较弱的一个方法。
DataReader类没有构造函数,所以不能直接实例化它,需要从Command对象中返回一个DataReader实例,具体做法是通过调用它们的ExecuteReader方法。
1. 创建DataReader对象
在ADO.NET中从来不会显式地使用DataReader对象的构造函数创建DataReader对象。事实上,DataReader类没有提供公有的构造函数。人们通常调用Command类的ExecuteReader方法,这个方法将返回一个DataReader对象。下面的代码阐明了如何创建SqlDataReader对象:
下面代码的功能是从表student中读取数据,并将数据列学号和姓名的所有数据输出到控制台:
String cnstr="server=(local); database=Student; Integrated Security=true";
SqlConnection cn=new SqlConnection(cnstr);
cn.Open();
string sqlstr=" select * from student";
SqlCommand cmd=new SqlCommand(sqlstr, cn);
SqlDataReader dr=cmd.ExecuteReader( );
while(dr.Read())
{
String id=dr["学号"].ToString();
String name=dr["姓名"].ToString();
Console.WriteLine("学号:{0} 姓名:{1}", id, name);
}
dr.Close();
cn.Close();
DataReader类最常见的用法就是检索SQL查询或存储过程返回记录。另外DataReader 是一个连接的、只向前的和只读的结果集。也就是说,当使用数据阅读器时,必须保持连接处于打开状态。除此之外,可以从头到尾遍历记录集,而且也只能以这样的次序遍历,即只能沿着一个方向向前的方式遍历所有的记录,并且在此过程中数据库连接要一直保持打开状态,否则将不能通过DataReader读取数据。这就意味着,不能在某条记录处停下来向回移动。记录是只读的,因此数据阅读器类不提供任何修改数据库记录的方法。
注意:
数据阅读器使用底层的连接,连接是它专有的。当数据阅读器打开时,不能使用对应的连接对象执行其他任何任务,例如执行另外的命令等。当阅读完数据阅读器的记录或不再需要数据阅读器时,应该立刻关闭数据阅读器。
在完成数据读取后,需要调用Close()方法关闭DataReader对象。如果创建DataReader对象时,使用的是ExecuteReader方法的另一个重载,代码如下:
SqlDataReader myDataReader=cmd.ExecuteReader(CommandBehavior.CloseConnection);
则关闭DataReader对象时会自动关闭底层连接,不再需要显示调用Connection对象的Close()方法关闭它。
Command对象的Execute方法有一个重载版本,那个重载版本接受命令行为参数。虽然命令文本指定返回结果集的查询,但是通过执行命令行为,可以提供一些关于想要怎么样使用结果的指令。ADO.NET在System.Dara命名空间中定义了CommandBehavior枚举,其值和具体意义如表4.8所示。
表4.8 CommandBehavior枚举
成员名称 |
说明 |
CloseConnection |
在执行该命令时,如果关闭关联的DataReader对象,则关联的Connection对象也将关闭 |
Default |
此查询可能返回多个结果集。执行查询可能会影响数据库状态。Default不设置CommandBehavior标志,因此调用 ExecuteReader(CommandBehavior.Default)在功能上等效于调用ExecuteReader() |
KeyInfo |
此查询返回列和主键信息。执行此查询时不锁定选定的行。 注意:当使用KeyInfo时,用于SQL Server的.NET Framework数据提供程序将FOR BROWSE子句追加到正在执行的语句。用户应该注意潜在的副作用,例如对SET FMTONLY ON语句的使用产生的干扰 |
SchemaOnly |
此查询只返回列信息,而不影响数据库状态 |
SequentialAccess |
提供一种方法,以便DataReader处理包含带有二进制值的列的行。 SequentialAccess不是加载整行,而是使DataReader将数据作为流来加载。然后可以使用GetBytes或GetChars方法来指定开始读取操作的字节位置以及正在返回的数据的有限的缓冲区大小。 当指定SequentialAccess时,尽管无需读取每个列,但是需要按照列的返回顺序读取它们。一旦已经读过返回的数据流中某个位置的内容,就不能再从DataReader中读取该位置或该位置之前的数据。当使用OleDbDataReader时,可重新读取当前列的值,直到读过它。当使用SqlDataReader时,一次只能读取一个列值 |
SingleResult |
查询返回一个结果集 |
SingleRow |
查询应返回一行。执行查询可能会影响数据库状态。一些.NET Framework数据 提供程序可能(但不要求)使用此信息来优化命令的性能。在用OleDbCommand对象的ExecuteReader方法指定SingleRow时,用于OLEDB的.NET Framework数据提供程序使用OLE DB IRow接口(如果可用)执行绑定。否则,它使用,IRowset接口。如果您的SQL语句应该只返回一行,则指定SingleRow还可以提高应用程序性能。 在执行返回多个结果集的查询时,可以指定SingleRow。在这种情况下,仍返回多个结果集,但每个结果集只有一行。 |
例如下面的代码表明当关闭阅读器reader时数据连接也将同时被关闭,无需再次显另的关闭数据连接。
SqlDataReader reader=cmd.ExecuteReader(CommandBehavior.CloseConnection);
2.遍历数据阅读器中的记录
当ExecuteReader方法返回DataReader对象时,当前光标的位置在第一条记录的前面。必须调用阅读器的Read方法把光标移动到第一条记录,然后,第一条记录将变成当前记录。如果数据阅读器所包含的记录不止一条,Read方法就返回一个Boolean值true。想要移到下一条记录,需要再次调用Read方法。重复上述过程,直到最后一条记录,那时Read方法将返回false。经常使用while循环来遍历记录:
while(reader.Read())
{
//读取数据
}
只要Read方法返回的值为true,就可以访问当前记录中包含的字段。
3.访问字段中的值
ADO.NET提供了两种方法访问记录中的字段。第一种是Item属性,此属性返回由字段索引或字段名指定的字段值。第二种方法是Get方法,此方法返回由字段索引指定字段的值。
DataReader类有一个索引符,可以使用常见的数组语法访问任何字段。使用这种方法,既可以通过指定数据列的名称,也可以通过指定数据列的编号来访问特定列的值。第一列的编号是0,第二列编号是1,依次类推。例如:
Object value1=myDataReader["学号"];
Object value1=myDataReader[0];
Item属性
每一个DataReader类都定义了一个Item属性,此属性返回一个代码字段属性的对象。Item属性是DataReader类的索引。需要注意的是Item属性总是基于0开始编号的:
object FieldValue=reader[FieldName];
object FieldValue=reader[FieldIndexl;
可以把包含字段名的字符串传入Item属性,也可以把指定字段索引的32位整数传递给Item属性。例如,如果命令是SQL select查询:
Select ID, cName from course
使用下面任意一种方法,都可以得到两个被返回字段的值:
object ID=reader ["ID"]
object cName=reader ["cName"];
或者:
object ID=reader[0];
object cName=reader [1];
另外需要注意的是,在使用数据时需要自己负责类型转换,如下所示:
int ID=(int)reader[0];
String cName=(string)reader[1];
注意:如果类型转换错误,例如将非数字类型转化为整型,将会在运行时抛出异常。
Get方法
除了通过索引访问数据外,DataReader类还有一组类型安全的访问方法可以用于读取指定列的值。这些方法是以Get开头的,并且它们的名称具有自我解释性。例如GetInt32()、GetString()等。这些方法都带有一个整数型参数,用于指定要读取列的编号。
每一个DataReader类都定义了一组Get方法,那些方法将返回适当类型的值。例如,GetInt32方法把返回的字段值作为32位整数。每一个Get方法都接受字段的索引,例如在上面的例子中,使用以下的代码可以检索ID字段和cName字段的值:
int ID=reader. Getint32 (0);
string cName=reader. GetString(1);
4.4.2 实例演示DataReader
在这个例子中,将读取所有的学生信息并将其显示出来。
(1)启动Visual Studio,新建一个名为DataReaderTest的WindowsApplication项目。
(2)在Forml.cs的空白处双击鼠标,进入Page—Load事件。Page_Load事件在页面加
载时执行。
(3)首先在Forml.cs中添加SqlClient的命名空间:
using System. Data. SqlClient;
(4)在Page_Load事件中添加如下代码:
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.SqlClient;
namespace DataReaderTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//定义输出消息
string message="";
//新建连接对象
SqlConnection conn=new SqlConnection();
conn.ConnectionString="Data Source=(local);Initial Catalog=Student;Integrated Security=SSPI";
//拼接命令字符串
string selectQuery="select ID, sName,sGrade,sSex from studentInfo";
//新建命令对象
SqlCommand cmd=new SqlCommand(selectQuery, conn);
conn.Open( );
//关闭阅读器时将自动关闭数据库连接
SqlDataReader reader=cmd. ExecuteReader(CommandBehavior.CloseConnection);
//循环读取信息
while (reader.Read())
{
message+="学号:"+reader[0].ToString()+" ";
message+="姓名:"+reader["sName"].ToString()+" ";
message+="班级:"+reader.GetString(2)+ " ";
message+="性别:"+reader.GetString(3)+" ";
message+="\n";
}
//关闭数据阅读器
//无需关闭连接,它将自动被关闭
reader.Close();
//测试数据连接是否已经关闭
if(conn.State==ConnectionState.Closed)
{
message+="数据连接已经关闭\n";
}
MessageBox.Show(message);
}
}
}
(5)编译项目,查看Forml.cs,可以得到如图4.19所示界面。
图4.19项目运行效果
代码讲解
(1)首先引入System.Data.SqlClient表示使用SQL Server.NET数据提供程序,然后建立连接和命令对象,并调用命令对象的ExecuteReader方法来返回DataReader对象:
SqlDataReader reader=cmd.ExecuteReader(CommandBehavior.CloseConnection);
其中,参数CommandBehavior.CloseConnection表明关闭数据阅读器时将同时关闭数据连接。
(2)接着用while循环读取字段并显示出来,读取字段共用到了前面介绍的3种方式:
//循环读取信息
while (reader.Read())
{
message+="学号:"+reader[0].ToString()+" ";
message+="姓名:"+reader["sName"].ToString()+" ";
message+="班级:"+reader.GetString(2)+ " ";
message+="性别:"+reader.GetString(3)+" ";
message+="\n";
}
(3)最后关闭阅读器:
reader.Close();
(4)此时数据连接已经关闭了,如下的代码测试了数据连接是否已经关闭,如果确实关闭了,则将在屏幕上输出提示信息:
//测试数据连接是否关闭
if(conn.State==ConnectionState.Closed)
{
message+="数据连接已经关闭\n";
}
从图4.19中可以看到数据连接确实已经关闭。
需要说明的是,在实际的Windows Application项目中直接访问DataReader中字段的机会不是很多,一般都是直接通过数据绑定来实现的,关于数据绑定技术将在后面章节介绍。
2.4 DataReader对象与数据获取
DataReader对象以“基于连接”的方式来访问数据库。也就是说,在访问数据库、执行SQL操作时,DataReader要求一直连在数据库上。这将会给数据库的连接负载带来一定的压力,但DataReader对象的工作方式将在很大程度上减轻这种压力。
2.4.1 DataReader对象的常用属性
DataReader对象提供了用顺序的、只读的方式读取用Command对象获得的数据结果集。由于DataReader只执行读操作,并且每次只在内存缓冲区里存储结果集中的一条数据,所以使用DataReader对象的效率比较高,如果要查询大量数据,同时不需要随机访问和修改数据,DataReader是优先的选择。
DataReader对象有以下常用属性。
Ø FieldCount属性:该属性用来表示由DataReader得到的一行数据中的字段数。
Ø HasRows属性:该属性用来表示DataReader是否包含数据。
Ø IsClosed属性:该属性用来表示DataReader对象是否关闭。
2.4.2 DataReader对象的常用方法
同样,在SQL Server Data Provider里的DataReader对象叫SqlDataReader,而在OLE DB Data Provider里叫OleDbDataReader。
DataReader对象使用指针的方式来管理所连接的结果集,它的常用方法有关闭方法、读取记录集下一条记录和读取下一个记录集的方法、读取记录集中字段和记录的方法,以及判断记录集是为空的方法。
1.Close方法
Close方法不带参数,无返回值,用来关闭DataReader对象。由于DataReader在执行SQL命令时一直要保持同数据库的连接,所以在DataReader对象开启的状态下,该对象所对应的Connection连接对象不能用来执行其他的操作。所以,在使用完DataReader对象时,一定要使用Close方法关闭该DataReader对象,否则不仅会影响到数据库连接的效率,更会阻止其他对象使用Connection连接对象来访问数据库。
2.bool Read()方法
bool Read()方法会让记录指针指向本结果集中的下一条记录,返回值是true或false。当Command的ExecuteReader方法返回DataReader对象后,须用Read方法来获得第一条记录;当读好一条记录想获得下一下记录时,也可以用Read方法。如果当前记录已经是最后一条,调用Read方法将返回false。也就是说,只要该方法返回true,则可以访问当前记录所包含的字段。
3.bool NextResult()方法
bool NextResult()方法会让记录指针指向下一个结果集。当调用该方法获得下一个结果集后,依然要用Read方法来开始访问该结果集。
4.Object GetValue(int i)方法
ObjectGetValue(int i)方法根据传入的列的索引值,返回当前记录行里指定列的值。由于事先无法预知返回列的数据类型,所以该方法使用Object类型来接收返回数据。
5.int GetValues(Object[] values)方法
int GetValues(Object[] values)方法会把当前记录行里所有的数据保存到一个数组里并返回。可以使用FieldCount属性来获知记录里字段的总数,据此定义接收返回值的数组长度。
6.获得指定宇段的方法
获得指定字段的方法有GetString、GetChar、GetInt32等,这些方法都带有一个表示列索引的参数,返回均是Object类型。用户可以根据字段的类型,通过输入列索引,分别调用上述方法,获得指定列的值。
例如,在数据库里,id的列索引是0,通过
string id=GetString(0);
代码可以获得id的值。
7.返回列的数据类型和列名的方法
可以调用GetDataTypeName()方法,通过输入列索引,获得该列的类型。这个方法的定义是:
string GetDataTypeName( int i)
可以调用GetName()方法,通过输入列索引,获得该列的名称。这个方法的定义是:
string GetName(int i);
综合使用上述两方法,可以获得数据表里列名和列的字段。
8.bool IsDBNull(int i)方法:
bool IsDBNull(int i)方法的参数用来指定列的索引号,该方法用来判断指定索引号的列的值是否为空,返回Tree或False。
2.4.3 DataReader对象访问数据库代码示例
下面的代码将说明如何利用DataReader对象获得并访问结果集。
//连接字符串
private static string strConnect=" data source=localhost;
uid=sa;pwd=aspent;database=LOGINDB"
SqlConnetion objConnection =new SqlConnection(strConnect);
SqlCommand objCommand =new SqlCommand( " ",objConnection);
// 设置查询类的SQL语句
objCommand.CommandText= " SELECT *FROM USERS ";
try
{
//打开数据库连接
if( objConnection.State == ConnectionState. Closed )
{
objConnection.Open();
}
//获取运行结果
SqlDataReader result=objCommand.ExecuteReader();
//如果DataRead对象成功获得数据,返回true,否则返回false
If(result.Read()==true)
{
//输出结果集中的各个字段
Response.Write(result["USERID"].ToString());
Response.Write(result["NICKNAME"].ToString());
Response.Write(result["USERROLE"].ToString());
}
}
catch(SqlException e)
{
Response.Write(e.Message.ToString());
}
finally
{
//关闭数据库连接
if(objConnection.State == ConnectionState.Open)
{
objConnection.Close();
}
//关闭DataRead对象
if(result.IsClosed == false)
{
reuslt.Close();
}
}
在代码里,给出了两种使用DataReader对象访问结果集的方式,一种是直接根据字段名,利用result["USERID"]的形式获得特定字段的值; 另一种方式写在注释里,通过for循环,利用FieldCount属性和GetValue方法,依次访问数据集的字段。
DataReader提供未缓冲的数据流,该数据流使过程逻辑可以有效地按顺序处理从数据源中返回的结果。由于数据不在内存中缓存,所以在检索大量数据时,DataReader是一种适合的选择。另外值得注意的是,DataReader在读取数据时,限制每次只能读一条,这样无疑提高了读取效率,一般适用于返回结果只有一条数据的情况。如果返回的是多条记录,就要慎用此对象。
2.5 DataAdaDter对象
DataAdapter对象主要用来承接Connection和DataSet对象。DataSet对象只关心访问操作数据,而不关心自身包含的数据信息来自哪个Connection连接到的数据源,而Connection对象只负责数据库连接而不关心结果集的表示。所以,在ASP.NET的架构中使用DataAdapter对象来连接Connection和DataSet对象。
另外,DataAdapter对象能根据数据库里的表的字段结构,动态地塑造DataSet对象的数据结构。
2.5.1 DataAdapter对象的常用属性
DataAdapter对象的工作步骤一般有两种,一种是通过Command对象执行SQL语句,将获得的结果集填充到DataSet对象中;另一种是将DataSet里更新数据的结果返回到数据库中。
DataAdapter对象的常用属性形式为XXXCommand,用于描述和设置操作数据库。使用DataAdapter对象,可以读取、添加、更新和删除数据源中的记录。对于每种操作的执行方式,适配器支持以下4个属性,类型都是Command,分别用来管理数据操作的“增”、“删”、 “改”、“查”动作。
Ø SelectCommand属性:该属性用来从数据库中检索数据。
ØInsertCommand属性:该属性用来向数据库中插入数据。
Ø DeleteCommand属性:该属性用来删除数据库里的数据。
ØUpdateCommand属性:该属性用来更新数据库里的数据。
例如,以下代码能给DataAdapter对象的selectCommand属性赋值。
//连接字符串
SqlConnection conn;
//创建连接对象conn语句
//创建DataAdapter 对象
SqlDataAdapter da=new SqlDataAdapter;
//给DataAdapter对象SelectCommand属性赋值
da.SelectCommand =new SqlCommand(" select * from user ", conn);
//后继代码
同样,可以使用上述方式给其他的InsertCommand、DeleteCommand和UpdateCommand属性赋值。
当在代码里使用DataAdapter对象的SelectCommand属性获得数据表的连接数据时,如果表中数据有主键,就可以使用CommandBuilder对象来自动为这个DataAdapter对象隐形地生成其他3个InsertCommand、DeleteCommand和UpdateCommand属性。这样,在修改数据后,就可以直接调用Update方法将修改后的数据更新到数据库中,而不必再使用InsertCommand、DeleteCommand和UpdateCommand这3个属性来执行更新操作。
2.5.2 DataAdapter对象的常用方法
DataAdapter对象主要用来把数据源的数据填充到DataSet中,以及把DataSet里的数据更新到数据库,同样有SqlDataAdapter和OleDbAdapter两种对象。它的常用方法有构造函数、填充或刷新DataSet的方法、将DataSet中的数据更新到数据库里的方法和释放资源的方法。
1.构造函数
不同类型的Provider使用不同的构造函数来完成DataAdapter对象的构造。对于SqlDataAdapter类,其构造函数说明如表2-8所示。
表2-8 SqIDataAdapter类构造函数说明
函数定义 |
参数说明 |
函数说明 |
SqlDataAdapter() |
不带参数 |
创建SqlDataAdapter对象 |
SqlDataAdapter(SqlCommand selectCommand |
selectCommand:指定新创建对象的SelectCommand属性 |
创建SqlDataAdapter对象。用参数selectCommand设置其Select Command属性 |
SqlDataAdapter(string selectCommandText, SqlConnection selectConnection) |
selectCommandText:指定新创建对象的SelectCommand属性值 selectConnection:指定连接对象 |
创建SqlDataAdapter对象。用参数selectCommandText设置其Select Command属性值,并设置其连接对象是selectConnection |
SqlDataAdapter(string selectCommandText,String selectConnectionString |
selectCommandText:指定新创建对象的SelectCommand属性值 selectConnectionString:指定新创建对象的连接字符串 |
创建SqlDataAdapter对象。将参数selectCommandText设置为Select Command属性值,其连接字符串是selectConnectionString |
O1eDbDataAdapter的构造函数类似SqlDataAdapter的构造函数,如下表2-9所述。
表2-9 OleDbDataAdaDter类构造函数说明
函数定义 |
参数说明 |
函数说明 |
OleDbDataAdapter() |
不带参数 |
创建OleDbDataAdapter对象 |
OleDbDataAdapter( OleDbCommand selectCommand) |
selectCommand:指定新创建对象的SelectCommand属性 |
创建OleDbDataAdapter对象。用参数selectCommand设置其SelectCommand属性 |
OleDbDataAdapter(string selectCommandText, OleDbConnection selectConnection)
|
selectCommandText: 指定新创建对象的SelectCommand属性值 selectConnection:指定连接对象 |
创建SqlDataAdapter对象。用参数selectCommandText设置其SelectCommand属性值,并设置其连接对象是selectConnection |
OleDbDataAdapter(string selectCommandText,Stnng selectConnectionString) |
selectCommandText:指定新创建对象的SelectCommand属性值 selectConnectionString:指定新创建对象的连接字符串 |
创建OleDbDataAdapter对象。将参数selectCommandText设置为SelectCommand属性值,其连接字符串是selectConnectionString |
2.Fill类方法.
当调用Fill方法时,它将向数据存储区传输一条SQL SELECT语句。该方法主要用来填充或刷新DataSet,返回值是影响DataSet的行数。该方法的常用定义如表2-10所示。
表2-10 DataAdapter类的刚I方法说明
函数定义 |
参数说明 |
函数说明 |
int Fill(DataSet dataset) |
dataset:需要更新的DataSet |
根据匹配的数据源,添加或更新参数所指定的DataSet,返回值是影响的行数 |
int Fill(DataSet dataset, string srcTable) |
dataset:需要更新的DataSet srcTable:填充DataSet的dataTable名 |
根据dataTable名填充DataSet |
3.int Update(DaraSetdataSet)方法
当程序调用Update方法时,DataAdapter将检查参数DataSet每一行的RowState属性,根据RowState属性来检查DataSet里的每行是否改变和改变的类型,并依次执行所需的INSERT、UPDATE或DELETE语句,将改变提交到数据库中。这个方法返回影响DataSet的行数。更准确地说,Update方法会将更改解析回数据源,但自上次填充DataSet以来,其他客户端可能已修改了数据源中的数据。若要使用当前数据刷新DataSet,应使用DataAdapter和Fill方法。新行将添加到该表中,更新的信息将并入现有行。Fill方法通过检查DataSet中行的主键值及SelectCommand返回的行来确定是要添加一个新行还是更新现有行。如果Fill方法发现DataSet中某行的主键值与SelectCommand返回结果中某行的主键值相匹配,则它将用SelectCommand返回的行中的信息更新现有行,并将现有行的RowState设置为Unchanged。如果SelectCommand返回的行所具有的主键值与DataSet中行的任何主键值都不匹配,则Fill方法将添加RowState为Unchanged的新行。
2.5.3 DataAdapter对象代码示例
下面的代码将说明如何利用DataAdapter对象填充DataSet对象。
private static string strConnect=" data source=localhost;
uid=sa;pwd=aspent;database=LOGINDB"
string sqlstr=" select * from USER ";
//利用构造函数,创建DataAdapter
SqlDataAdapter da=new SqlDataAdapter(sqlstr, strConnect);
// 创建DataSet
DataSet ds=new DataSet();
//填充,第一个参数是要填充的dataset对象,第二个参数是填充dataset的datatabble
da.Fill(ds, "USER" );
上述代码使用DataApater对象填充DataSet对象的步骤如下。
(1)根据连接字符串和SQL语句,创建一个SqlDataAdapter对象。这里,虽然没有出现Connection和Command对象的控制语句,但是SqlDataAdapter对象会在创建的时候, 自动构造对应的SqlConnection和SqlCommand对象,同时根据连接字符串自动初始化连接。要注意的是,此时SqlConnection和SqlCommand对象都处于关闭状态。
(2)创建DataSet对象,该对象需要用DataAdapter填充。
(3)调用DataAdapter的Fill方法,通过DataTable填充DataSet对象。由于跟随DataAdapter对象创建的Command里的SQL语句是访问数据库里的USER表,所以在调用Fill方法的时候,在打开对应的SqlConnection和SqlCommand对象后,会用USER表的数据填充创建一个名为USER的DataTable对象,再用该DataTable填充到DataSet中。
下面的代码演示了如何使用DataAdapter对象将DataSet中的数据更新到数据库。
private static string strConnect=" data source=localhost;
uid=sa;pwd=aspent;database=LOGINDB"
string sqlstr=" select * from USER ";
//利用构造函数,创建DataAdapter
SqlDataAdapter da=new SqlDataAdapter(sqlstr, strConnect);
// 创建DataSet
DataSet ds=new DataSet();
//填充,第一个参数是要填充的dataset对象,第二个参数是填充dataset的datatabble
da.Fill(ds, "USER" );
//以下代码将更新DataSet里的数据
//在DataSet里的名为"USER"的DataTable里添加一个用于描述行记录的DataRow对象
DataRow dr=ds.Tables["USER"].NewRow();
//通过DataRow对象添加一条记录
dr["USERID"]="ID2" ;
dr["USERNAME"]="TOM" ;
ds.Tables["USER"].Rows.Add(dr);
//更新到数据库里
SqlCommandBuilder scb=new SqlCommandBuilder(da);
da.update(ds, "USER");
在上述代码里,首先使用DataAdapter填充DataSet对象,然后通过DataRow对象,向DataSet添加一条记录,最后使用DataSet的update方法将添加的记录提交到数据库中。执行完update语句,数据库USER中就多了一条USERID是ID2、USERNAME是TOM的记录。
此外,上述代码出现的SqlCommandBuilder对象用来对数据表进行操作。用了这个对象,就不必再繁琐地使用DataAdapter的UpdataCommand属性来执行更新操作。
2.6 DataSet对象
DataSet对象可以用来存储从数据库查询到的数据结果,由于它在获得数据或更新数据后立即与数据库断开,所以程序员能用此高效地访问和操作数据库。并且,由于DataSet对象具有离线访问数据库的特性,所以它更能用来接收海量的数据信息。
2.6.1 DataSet对象概述
DataSet是ADO.NET中用来访问数据库的对象。由于其在访问数据库前不知道数据库里表的结构,所以在其内部,用动态XML的格式来存放数据。这种设计使DataSet能访问不同数据源的数据。
DataSet对象本身不同数据库发生关系,而是通过DataAdapter对象从数据库里获取数据并把修改后的数据更新到数据库。在DataAdapter的讲述里,就已经可以看出,在同数据库建立连接后,程序员可以通过DataApater对象填充(Fill)或更新(Update)DataSet对象。
.NET的这种设计,很好地符合了面向对象思想里低耦合、对象功能唯一的优势。如果让DataSet对象能直接连到数据库,那么DataSet对象的设计势必只能是针对特定数据库,通用性就非常差,这样对DataSet的动态扩展非常不利。
由于DataSet独立于数据源,DataSet可以包含应用程序本地的数据,也可以包含来自多个数据源的数据。与现有数据源的交互通过DataAdapter来控制。
DataSet对象常和DataAdapter对象配合使用。通过DataAdapter对象,向DataSet中填充数据的一般过程是:
(1)创建DataAdapter和DataSet对象。
(2)使用DataAdapter对象,为DataSet产生一个或多个DataTable对象。
(3)DataAdapter对象将从数据源中取出的数据填充到DataTable中的DataRow对象里,然后将该DataRow对象追加到DataTable对象的Rows集合中。
(4)重复第(2)步,直到数据源中所有数据都已填充到DataTable里。
(5)将第(2)步产生的DataTable对象加入DataSet里。
而使用DataSet,将程序里修改后的数据更新到数据源的过程是:
(1)创建待操作DataSet对象的副本,以免因误操作而造成数据损坏。
(2)对DataSet的数据行(如DataTable里的DataRow对象)进行插入、删除或更改操作,此时的操作不能影响到数据库中。
(3)调用DataAdapter的Update方法,把DataSet中修改的数据更新到数据源中。
2.6.2 DataSet对象模型
从前面的讲述中可以看出,DataSet对象主要用来存储从数据库得到的数据结果集。为了更好地对应数据库里数据表和表之间的联系,DataSet对象包含了DataTable和DataRelation类型的对象。
其中,DataTable用来存储一张表里的数据,其中的DataRows对象就用来表示表的字段结构以及表里的一条数据。另外,DataTable中的DataView对象用来产生和对应数据视图。而DataRelation类型的对象则用来存储DataTable之间的约束关系。DataTable和DataRelation对象都可以用对象的集合(Collection)对象类管理。
由此可以看出,DataSet中的方法和对象与关系数据库模型中的方法和对象一致,DataSet对象可以看作是数据库在应用代码里的映射,通过对DataSet对象的访问,可以完成对实际数据库的操作。DataSet的对象模型如图2-2所示。
图2-2 DataSet对象模型
DataSet对象模型中的各重要组件说明如下。
1.DataRelationCollection和DataRelation
DataRelation对象用来描述DataSet里各表之间的诸如主键和外键的关系,它使一个DataTable中的行与另一个DataTable中的行相关联,也可以标识DataSet中两个表的匹配列。
DataRelationCollection是DataRelation对象的集合,用于描述整个DataSet对象里数据表之间的关系。
2.ExtendedProperties
DataSet、DataTable和DataColumn全部具有ExtendedProperties属性。可以在其中加入自定义信息,例如用于生成结果集的SQL语句或生成数据的时间。
3.DataTableCollection和DataTable
在DataSet里,用DataTable对象来映射数据库里的表,而DataTableCollection用来管理DataSet下的所有DatabTable。
DataTable具有以下常用属性。
(1)TableName:用来获取或设置DataTable的名称。
(2)DataSet:用来表示该DataTable从属于哪个DataSet。
(3)Rows:用来表示该DataTable的DataRow对象的集合,也就是对应着相应数据表里的所用记录。程序员能通过此属性,依次访问DataTable里的每条记录。该属性有如下方法。
Ø Add:把DataTable的AddRow方法创建的行追加到末尾。
Ø InsertAt:把DataTable的AddRow方法创建的行追加到索引号指定的位置。
Ø Remove:删除指定的DataRow对象,并从物理上把数据源里的对应数据删除。
Ø RemoveAt:根据索引号,直接删除数据。
(4)Columns:用来表示该DataTable的DataColumn对象的集合,通过此属性,能依次访问DataTable里的每个字段。
DataTable具有以下常用方法。
Ø DataRow NewRow()方法:该方法用来为当前的DataTable增加一个新行,返回表示行记录的DataRow对象,但该方法不会把创建好的DataRow添加到DataRows集合中,而是需要通过调用DataTable对象Rows属性的Add方法,才能完成添加动作。
Ø DataRow [] Select()方法:该方法执行后,会返回一个DataRow对象组成的数组。
Ø void Merge(DataTabletable)方法:该方法能把参数中的DataTable和本DataTable合并。
Ø void Load(1DataReaderreader)方法:该方法通过参数里的IdataReader对象,把对应数据源里的数据装载到DataTable里,以便后继操作。
Ø void Clear()方法:该方法用来清除DataTable里的数据,通常在获取数据前调用。
Ø void Reset()方法:该方法用宋重置DataTabl对象。
2.6.3 DataCOIumn和DataRow对象
在DataTable里,用DataColumn对象来描述对应数据表的字段,用DataRow对象来描述对应数据库的记录。
值得注意的是,DataTable对象一般不对表的结构进行修改,所以一般只通过Column对象读列。例如,通过DataTable.Table[”TableName"].Column[columnName]来获取列名。
DataColumn对象的常用属性如下。
Ø Caption属性:用来获取和设置列的标题。
Ø ColumnName属性:用来描述该DataColumn在DataColumnCollection中的名字。
Ø DataType属性:用来描述存储在该列中数据的类型。
在DataTable里,用DataRow对象来描述对应数据库的记录。
DataRow对象和DataTable里的Rows属性相似,都用来描述DataTable里的记录。同ADO版本中的同类对象不同的是,ADO.NET下的DataRow有“原始数据”和“已经更新的数据”之分,并且,DataRow中的修改后的数据是不能即时体现到数据库中的,只有调用DataSet的Update方法,才能更新数据。
DataRow对象的重要属性有RowState属性,用来表示该DataRow是否被修改和修改方式。RowState属性可以取的值有Added、Deleted、Modified或Unchanged。
而DataRow对象有以下重要方法。
Ø void AcceptChanges()方法:该方法用来向数据库提交上次执行AcceptChanges方法后对该行的所有修改。
Ø void Delete()方法:该方法用来删除当前的DataRow对象。
- Ø 设置当前DataRow对象的RowState属性的方法:此类方法有:
void SetAdded( ) ;
void SetModified();
分别用来把DataRow对象设置成Added和Modified。
Ø void AcceptChanges()方法:该方法用来向数据库提交上次执行AcceptChanges方法后对该行的所有修改。
Ø void BeginEdit()方法:该方法用来对DataRow对象开始编辑操作。
Ø void cancelEdit()方法:该方法用来取消对当前DataRow对象的编辑操作。
Ø void EndEdit()方法:该方法用来终止对当前DataRow对象的编辑操作。
下面的代码讲述了如何综合地使用DataTable、DataColumn和DataRow对象进行数据库操作。
private void DemonstrateRowBeginEdit( )
{
//创建DataTable对象
DataTable table=new DataTable("table1");
//创建DataColumn对象,并设置其属性为Int32类型
DataColumn column=new DataColumn("col1", Type.GetType(" System.Int32" ));
// 添加Column到dataTable中
table.Columns.Add(column);
//使用for循环,创建5个DataRow对象并添加到DataTable中
DataRow newRow;
for(int i=0; i<5; i++)
{
// RowChanged event will occur for every addition
newRow=table.NewRow();
newRow[0]=i;
table.Rows.Add(newRow);
}
//使用dataTable的AcceptChanges方法,将更改提交到数据库中
table.AcceptChanges();
//开始操作DataRow中的每个对象
foreach(DataRow row in table.Rows)
{
//使用BeginEdit方法开始操作
row.BeginEdit();
row[0]=(int) row[0]+10;
}
table.Rows[0].BeginEdit();
table.Rows[1].BeginEdit();
table.Rows[0][0]=100;
table.Rows[1][0]=100;
try
{
//终止对DataRow对象进行操作
table.Rows[0].EndEdit();
table.Rows[1].EndEdit();
}
catch(Exception e)
{
//出错处理
Console.WriteLine(" Exception of type {0} occurred. " , e.GetType() );
}
}
上述代码的主要业务逻辑如下:
(1)创建DataTable和DataColumn类型的对象,并把DataColumn对象的数据类型设置成System.Int32。也就是说,使用该DataColumn对象可以对应地接收int类型的字段数据。
(2)把DataColumn对象添加到DataTable中。
(3)依次创建5个DaaRow对象,同时通过for循环给其赋值。完成赋值后,将这5个DataRow对象添加到DataTable中。
(4)使用AcceptChanges方法,实现DataColumn和DataRow对象的更新。
(5)使用BeginEdit方法,开始编辑DataRow对象,使用EndEdit方法来表示编辑结束。
使用DataTable、DataColumn和DmaRow对象访问数据的一般方式有以下几种。
(1)使用Table名和Table索引来访问DataTable。为了提高代码的可读性,推荐使用Table名的方式来访问Table。代码如下:
DataSet ds=new DataSet();
DataTable dt=new DataTble(" myTableName");
//向DataSet的Table里添加一个dataTable
ds.Tables.Add(dt);
//访问dataTable
//1 通过表名访问,推荐使用
ds.Tables["myTableName"].NewRow();
//2 通过索引访问,索引值从0开始,不推荐使用
ds.Tables[0].NewRow();
(2)使用Rows属性访问数据记录,例如:
foreach(DataRow row in table.Rows)
{
Row[0]=(int) row[0]+10;
}
(3)使用Rows属性,访问指定行的指定字段,例如:
//首先为DataTable对象创建一个数据列
DataTable table=new DataTable("table1");
DataColumn column=new DataColumn(" col1", Type.GetType("System.Int32"));
table.Columns.Add(column);
// 其次为DataTable添加行数据
newRow=table.NewRow();
newRow[0]=10;
table.Rows.Add(newRow);
//设置索引行是0,列名是col1的数据
table.Rows[0]["col1"]=100;
//设置索引行是0,索引列是0的数据,这种做法不推荐
//table.Rows[0][0]=100;
(4)综合使用DgaRow和DataColumn对象访问DataTable内的数据。从以下代码可以看出,DataTable对象中的Rows属性对应于它的DataRow对象,而Columns属性对应于DgaColumn。
foreach(DataRow dr in dt.Rows )
{
foreach(DataColumn dc in dt.Columns )
{
//用数组访问数据
Dr[dc]=100;
}
}
2.6.4 使用DataSet对象访问数据库
当对DataSet对象进行操作时,DataSet对象会产生副本,所以对DataSet里的数据进行编辑操作不会直接对数据库产生影响,而是将DataRow的状态设置为added、deleted或changed,最终的更新数据源动作将通过DataAdapter对象的update方法来完成。
DataSet对象的常用方法如下。
Ø void AcceptChanges():该方法用来提交DataSet里的数据变化。
Øvoid clear():该方法用来清空DataSet里的内容。
ØDataSet copy():该方法把DataSet的内容复制到其他DataSet中。
ØDataSet GetChanges():该方法用来获得在DataSet里已经被更改后的数据行,并把这些行填充到Dataset里返回。
Ø bool HasChanges():如果DataSet在创建后或执行AcceptChanges后,其中的数据没有发生变化,返回True,否则返回False。
Øvoid RejectChanges():该方法撤销DataSet自从创建或调用AcceptChanges方法后的所有变化。
DataSet对象一般是和DataAdapter对象配合使用。下面的代码演示了如何综合使用DataSet和DataAdapter对象访问数据库。
//省略获得连接对象的代码
……
//创建DataAdapter
string sql= " select * from user ";
SqlDataAdapter sda=new SqlDataAdapter(sql, conn);
// 创建并填充Dataset
DataSet ds=new DataSet();
sda.fill(ds, "user");
//给Dataset创建一个副本,操作对副本进行,以免因误操作而破坏数据
DataSet dsCopy=ds.Copy();
DataTable dt=ds.Table["user"];
//对DataTable中的DataRow和DataColumn对象进行操作
……
//最后将更新提交到数据库中
sda.update(ds, "user");
上述代码的主要业务流程如下。
(1)创建DataAdapter和DataSet对象,并用DataAdapter的SQL语句生成的表填充到
DataSet的DataTable中。
(2)使用DataTable对表进行操作,例如做增、删、改等动作。
(3)使用DataAdapter的update语句将更新后的数据提交到数据库中。
另外,上述代码在操作DataSet前,为DataSet创建了一个副本,用宋避免误操作。
2.7 ADO.NET 代码综合示例
前面已经介绍过OLE DB.NET和SQL Server.NET数据提供者可以用来连接不同的数据源。以下代码不仅综合演示了使用ADO.NET的这两种数据提供者访问数据库的一般步骤,而且说明了使用不同种类的ADO.NET组件集合访问数据库的一般步骤。可以通过代码进一步了解这两种数据提供者访问方式的异同之处。
2.7.1 使用OLE DB.NET Provider
OLE DB的数据提供者可以访问Access和SQL等数据库,代码如下:
//设置连接字符串
string dbname=@" user.mdb";
string db=Server.MapPath(dbPath);
string connectionString ="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +db;
//设置SQL语句
string strSQL=" select UserId, UserName from User";
//根据连接字符串,创建连接对象
OleDbConnection conn=new OleDbConnection(connectionString);
//根据连接对象和SQL语句,创建DataAdapter对象
OleDbDataAdapter da=new OleDbDataAdapter(strSQL, conn);
//创建OleDbCommandBuilder, 用来对DataAdapter更好地操作
OleDbCommandBuilder cb=new OleDbCommandBuilder(da);
上述代码演示了从根据连接字符串建立连接对象,到创建DataAdpate对象填充DataSet,再到使用DataSet对象提交数据更新以及与DataGrid绑定的一般过程。
上述代码也演示了使用Connection、DataAdapter和DataSet访问数据库的一般流程。ADO.NET采用离线的方式访问数据库,所以使用DataSet从数据库获得数据后的操作数据的过程中,不必保持与数据库的连接,直到向数据库提交更新数据时,才需连上数据库。这种做法适合于海量数据更新的情况。
而在上述代码里,因为数据操作过程比较简单,所以直到数据更新提交结束才断开连接,也就是说,并没有采用“离线”的数据库连接访问方式。不采用“离线”方式的理由是:在数据库访问操作中,频繁连接和断开数据源也需要耗费一定的系统资源,如果这个代价要比保持短时间的数据连接所需的代价大,那么宁可选择“一直在线”的连接方式。
2.7.2 使用SQL Server .NET Provider
以下的代码演示了如何使用SQLServer的.NETProvider来连接和访问数据。
//连接字符串
string connectionString="server=local;database=Northwind;user=sa;pwd=;";
//建立连接
Sqlconnection =new SqlConnection(connectionString);
//SQL语句
string strSql= "select UserID, UserName from User";
//根据连接对象和SQL语句创建SqlCommand对象
SqlCommand cmd=new SqlCommand(strSql, conn);
conn.Open();
//使用Command对象创建SqlDataReader对象
SqlDataReader reader=cmd.ExecuteReader();
//使用DataReader对象填充DataGrid 控件
DataGrid_User.DataSource=reader;
DataGrid_User.DataBind();
//关闭连接
conn.Close();
上述代码也演示了使用Connection、Command和DataReader对象访问数据库的一般方式。
2.7.3 数据库访问综述
前面讲述了用两种不同的数据提供者(Provider)访问连接数据库的方式,如果数据源是SQL Server,则使用SQL Server的Provider;如果数据源是ODBC或是其他类型的数据库,则可以选用OLE DB的Provider。
前面还讲述了使用Connection+Command+DataReader对象和使用Connection+ DataAdapter+DataSet对象的数据库访问方式。
根据DataReader的特性,以Connection+Command+DataReader方式访问数据库的使用场景有:
Ø 访问数据只用于显示,而不修改。
Ø 仅对一个数据源进行操作,或是对单表进行操作。
Ø 对于数据只希望向后顺序访问,而不进行重复遍历。
Ø 访问数据量小,不需要在内存中大量存储数据。
Ø 需要访问的结果集太大,不能一次性地全部放入内存,此时也能使用DataReader来逐次访问。
而DataSet支持离线的访问方式,可以有以下的应用:
Ø 同一业务逻辑需要访问多个数据源,比如同时要向SQL Server和Oracle数据库中请求数据。
Ø由于DataSet可以包含多个DataTable,如果需要访问的数据对象来自多个表,可用DataSet的Table来分别存储管理。
Ø如果访问操作的数据量比较大,利用DataSet的离线访问机制可以减轻对数据库负载的压力。
2.8 DataGrid控件与数据库访问技术
在实际的应用项目中,通常需要把用ADO.NET组件获得的数据信息显示在界面上,供用户浏览或修改。可以通过使用.NET的DataGrid控件实现这种功能。
2.8.1 DataGrid控件与数据绑定
DataGrid控件的主要目的是实现“数据绑定”(Data Binding),即把DataGrid控件上显示的数据同后台数据库的数据绑定在一起,同步地一起变化。
另外,DataGrid控件以表格的形式显示了查询到的数据结果集,默认的访问方式是只读而不能修改,通过设置,可以实现记录的修改和删除功能。
2.8.2 DataGrid代码示例
通过以下的步骤,能将数据库里的数据动态绑定到DataGrid对象里并显示。
(1)在D盘下建立一个Access类型的数据库,命名为Student.mdb。在其中新建一张Studentlnfo的表,其中的字段如表2-11所示。
表 1 Studentlnfo表字段说明
字 段 |
中文描述 |
数据类型 |
备 注 |
SID |
学号 |
文本 |
主键 |
SName |
姓名 |
文本 |
一 |
Sex |
性别 |
文本 |
一 |
注意:本书表中“一”表示无须设置相关信息。
完成后往其中插入一些记录,如(001,Tom,Male)。
(2)打开Visual Studio 2005环境后,选择“文件”|“新建”|“新建网站”命令,在弹出的。新建网站”对话框中选择“ASP.NET项目”,输入网站名testDataGrid和路径CAroot~DataGrid,登录模块所有代码和配置文件均放在此项目下。
(3)在集成开发环境中的“解决方案资源管理器”里,选中项目,右击,在弹出的快捷菜单中选择“添加新项”命令,新建一个Web配置文件,命名为Web.config。该配置文件主要用来管理登录模块的一些全局性数据。
其中,在配置文件里设置数据库的连接属性,使用OleDB的Data Provider,连接到Access数据源上,代码如下。
<configuration>
<appSettings>
<add key="connStr" value="Porvider=Microsoft.Jet.OleDB.4.0; Data Source=D:\login.mdb "></add>
</appSettings>
<connectionStrings />
(4)在集成开发环境中的“解决方案资源管理器”里,选中项目,右击,在弹出的快捷菜单中选择“添加新项”命令,新建一个Web窗体,命名为showDataGrid.aspx。
(5)打开“工具箱”的“Web窗体”,从中拖曳一个DataGrid控件到页面上,从DataGrid的属性栏中,可以看到该DataGrid对象叫DataGrid1。
选中DataGridView控件,右击,在弹出的快捷菜单中选择"属性"命令,在属性栏的Columns中单击“…”按钮,在弹出的“编辑列”对话框中,通过“添加”命令,依次向DataGridView控件里添加“学号”、“姓名”和“性别”3列,并把每列的DataPropertyName设置成该列对应的数据库字段,用于数据绑定。具体设置如表2-12所示。
表2-12 DataGridView的Columns属性数据列信息
数据列名 |
DataPropertyName |
学号 |
SID |
姓名 |
SName |
性别 |
Sex |
(6)双击窗体的空白处,进入相应的逻辑代码文件showDataGrid.aspx.cs。在该文件的顶端,添加数据库访问引用的命名空间语句:
using System.Data.SqlClient;
并在class的开头添加连接数据库的字符串定义:
Private static string strConnect =
System.Configuration.ConfigurationManager.AppSettings["connstr"];
然后在该页面的Page_Load方法里添加以下代码:
//
由于使用的是DataProvider,所以ADO.NET组件的对象都以OleDb开头。
上述代码的主要业务逻辑是:
(1)根据连接字符串,创建数据库连接对象,并根据连接对象创建OLeDbCommand对象。
(2)根据SQL语句,查询所有学生的信息,以SID的/顷序排列。
(3)使用OleDbDataApapter对象,将查询结果填充到DataSet中的。均Dataset数据表里。
(4)将DataGrid的数据源设置成。均Dataset数据表,实现数据绑定。
这样,当打开此页面时,DataGrid就能与Access数据库绑定,显示数据库里的学生信息。
2.9 本章小结
本章首先介绍了ADO.NET的体系结构,并在此基础上讲述了ADO.NET各组件的作用和使用方式。
其次,在2.7节里介绍了使用Connection、DataAdapter和DataSet对象访问修改数据库与使用Connection、Command和DataReader对象访问数据库的两种方式,希望读者能根据需求在不同场合中适当地使用这两种方式。
最后,在2.8节里介绍了数据绑定控件DataGrid的概念和一般使用方法,这个控件将在后续章节的项目描述中大量用到。