Asp.net夜話之七:ADO.NET介紹
ADO.NET是對(duì)Microsoft ActiveX Data Objects (ADO)一個(gè)跨時(shí)代的改進(jìn),它提供了平臺(tái)互用性和可伸縮的數(shù)據(jù)訪問。由于傳送的數(shù)據(jù)都是XML格式的,因此任何能夠讀取XML格式的應(yīng)用程序都可以進(jìn)行數(shù)據(jù)處理。事實(shí)上,接受數(shù)據(jù)的組件不一定要是ADO .NET組件,它可以是基于一個(gè)Microsoft Visual Studio的解決方案,也可以是任何運(yùn)行在其它平臺(tái)上的任何應(yīng)用程序。
以前做數(shù)據(jù)庫訪問的時(shí)候,需要一直與數(shù)據(jù)庫保持連接,直到獲取完所有滿足需要的數(shù)據(jù)之后才會(huì)斷開數(shù)據(jù)庫連接,這種數(shù)據(jù)庫訪問方式稱之為連接式數(shù)據(jù)訪問技術(shù)。相比于以前的連接式數(shù)據(jù)訪問技術(shù),ADO.NET除了提供連接式數(shù)據(jù)訪問技術(shù)之外,還提供了另一種斷開式解決方案,那就是在內(nèi)存中模擬一個(gè)數(shù)據(jù)庫,也就是內(nèi)存中的數(shù)據(jù)庫。
我們知道在實(shí)際的數(shù)據(jù)庫技術(shù)中,每個(gè)數(shù)據(jù)庫就是一個(gè)業(yè)務(wù)邏輯單元,一般來說這個(gè)數(shù)據(jù)庫包含了實(shí)現(xiàn)一個(gè)應(yīng)用軟件或者一個(gè)網(wǎng)站所需要的全部數(shù)據(jù)。在這里數(shù)據(jù)庫就是頂級(jí)對(duì)象,我們引用創(chuàng)建數(shù)據(jù)庫時(shí)所用到的名詞database來表示(因?yàn)閯?chuàng)建數(shù)據(jù)庫的SQL語句是create database),在一個(gè)數(shù)據(jù)庫里可以包含有多個(gè)表(table)和視圖(view),除此之外還可以包含有一些外鍵關(guān)系等。在一個(gè)表(table)或者視圖(view)里可以包含多個(gè)列(column)和行(row)。
在ADO.NET中對(duì)上面提到的對(duì)象都在內(nèi)存中進(jìn)行了模擬,在內(nèi)存中的數(shù)據(jù)庫對(duì)象稱之為DataSet,一個(gè)內(nèi)存中的數(shù)據(jù)庫(DataSet)可以包含多個(gè)在內(nèi)存中的表(DataTable)和內(nèi)存中的視圖(DataView),并且也允許在表存在一些關(guān)系(DataRelation)。同時(shí)在一個(gè)內(nèi)存中的表(DataTable)或者內(nèi)存中的視圖(DataView)中也允許存在行(DataRow)和列(DataColumn)。
物理數(shù)據(jù)庫與內(nèi)存數(shù)據(jù)庫之間的各對(duì)象的對(duì)應(yīng)關(guān)系如下:
在本篇將講述6個(gè)ADO.NET中的常用對(duì)象:
Connection對(duì)象
Command對(duì)象
DataReader對(duì)象
DataAdapter對(duì)象
DataSet對(duì)象
DataTable對(duì)象
DataRow對(duì)象
DataColumn對(duì)象
參數(shù)化SQL語句
分頁查詢SQL語句
嚴(yán)格地說,在.net類庫中并沒有Connection、Command、DataAdapter和DataReader對(duì)象的,這是對(duì)相關(guān)的對(duì)象做了一個(gè)抽象。在實(shí)際的開發(fā)中,我們經(jīng)常用到的數(shù)據(jù)庫有Access、SQL Server、Oracle、MySQL等,盡管大部分都遵循SQL國際化標(biāo)準(zhǔn),但是它們?cè)谧裱瓨?biāo)準(zhǔn)的前提下又做了一些擴(kuò)充,并且即使遵循了相同的標(biāo)準(zhǔn),但是實(shí)現(xiàn)方法并不相同,所以在某些情況下實(shí)現(xiàn)相同的功能可能在不同的數(shù)據(jù)庫中SQL語句并不相同。
于是,在ADO.NET也定義了一套用于訪問數(shù)據(jù)庫的標(biāo)準(zhǔn),當(dāng)然這個(gè)標(biāo)準(zhǔn)是以接口(interface)的形式提供的,各數(shù)據(jù)庫廠商只要實(shí)現(xiàn)了這個(gè)接口就能在ADO.NET下正常工作(這也是接口的作用,接口就是用于指定規(guī)范,自己本身并不實(shí)現(xiàn),在Java中針對(duì)數(shù)據(jù)庫訪問也有一套接口留待各數(shù)據(jù)庫來實(shí)現(xiàn))。當(dāng)然在.net類庫中微軟已經(jīng)提供對(duì)Access、SQL Server和Oracle數(shù)據(jù)庫對(duì)上面提到的接口的實(shí)現(xiàn)。
在ADO.NET中定義的這一套接口是IDbConnection、IDbCommand、IDbDataAdapter和IDataReader,并且還有一套實(shí)現(xiàn)這些接口的抽象類,分別是DbConnection、DbCommand、DbDataAdapter和DataReader。
Connection對(duì)象
Connection對(duì)象也稱為數(shù)據(jù)庫連接對(duì)象,Connection對(duì)象的功能是負(fù)責(zé)對(duì)數(shù)據(jù)源的連接。所有Connection對(duì)象的基類都是DbConnection類。
Connection對(duì)象有兩個(gè)重要屬性:
ConnectionString:表示用于打開 SQL Server 數(shù)據(jù)庫的字符串;
State:表示 Connection 的狀態(tài),有Closed和Open兩種狀態(tài)。
Connection對(duì)象有兩個(gè)重要方法:
Open()方法:指示打開數(shù)據(jù)庫;
Close()方法:指示關(guān)閉數(shù)據(jù)庫。
在實(shí)際開發(fā)中很多朋友經(jīng)常詢問數(shù)據(jù)庫連接字符串該怎么寫,其實(shí)有一個(gè)很簡單的技巧,我們可以在Visual Studio 2005工具箱中找到數(shù)據(jù)分組,如下圖:
IF EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name = N'AspNetStudy')
DROP DATABASE [AspNetStudy]
GO
CREATE DATABASE [AspNetStudy] ON (NAME = N'AspNetStudy_Data', FILENAME = N'C:/Program Files/Microsoft SQL Server/MSSQL/data/AspNetStudy_Data.MDF' , SIZE = 1, FILEGROWTH = 10%) LOG ON (NAME = N'AspNetStudy_Log', FILENAME = N'C:/Program Files/Microsoft SQL Server/MSSQL/data/AspNetStudy_Log.LDF' , SIZE = 1, FILEGROWTH = 10%)
COLLATE Chinese_PRC_CI_AS
GO
use [AspNetStudy]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[UserInfo]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[UserInfo]
GO
CREATE TABLE [dbo].[UserInfo] (
[UserID] [int] IDENTITY (1, 1) NOT NULL ,
[UserName] [varchar] (20) COLLATE Chinese_PRC_CI_AS NOT NULL ,
[RealName] [nvarchar] (8) COLLATE Chinese_PRC_CI_AS NOT NULL ,
[Age] [tinyint] NOT NULL ,
[Sex] [bit] NOT NULL ,
[Mobile] [char] (11) COLLATE Chinese_PRC_CI_AS NULL ,
[Phone] [char] (11) COLLATE Chinese_PRC_CI_AS NULL ,
[Email] [varchar] (50) COLLATE Chinese_PRC_CI_AS NOT NULL
) ON [PRIMARY]
GO
CREATE UNIQUE INDEX [IX_UserName] ON [dbo].[UserInfo]([UserName]) WITH IGNORE_DUP_KEY ON [PRIMARY]
GO
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('zhangfei','張飛',36,'13455663420','03517890360','zhangfei@msn.com',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('guanyu','關(guān)羽',38,'13455663421','03517890361','guanyu@163.com',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('liubei','劉備',42,'13455663422','03517890362','liubei@163.net',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('zhougong','周公',29,'13455663423','03517890363','zhoufoxcn@tom.com',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('zhaoyun','趙云',32,'13455663424','03517890364','zhaoyun@sohu.com',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('huanggai','黃蓋',50,'13455663425','03517890365','huanggai@live.com',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('zhugeliang','諸葛亮',27,'13455663426','03517890366','zhugeliang@hotmail.com',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('jiangwei','姜維',22,'13455663427','03517890367','jiangwei@netease.com',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('caocao','曹操',48,'13455663428','03517890368','caocao@qq.com',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('guojia','郭嘉',32,'13455663429','03517890369','guojia@21cn.com',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('sunquan','孫權(quán)',33,'13455663430','03517890370','sunquan@gmail.com',1);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('diaochan','貂禪',20,'13455663431','03517890371','diaochan@sina.com.cn',0);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('yangyuhuan','楊玉環(huán)',24,'13455663432','03517890372','yangyuhuang@chinaren.com',0);
insert into UserInfo([UserName],[RealName],[Age],[Mobile],[Phone],[Email],[Sex])values('wangzhaojun','王昭君',26,'13455663433','03517890373','wangzhaojun@yahoo.com.cn',0);
go
我們可以點(diǎn)擊一下“測試連接”按鈕,如果彈出連接成功的提示消息就表示這個(gè)數(shù)據(jù)庫連接是可用的。點(diǎn)擊“確定”按鈕,回到“配置數(shù)據(jù)源”界面,這時(shí)候點(diǎn)擊連接字符串旁邊的”+”按鈕就可以看到數(shù)據(jù)庫的連接字符串信息,如下圖所示:
<%@ Page Language='C#' %>
<%@ Import Namespace='System.Data.SqlClient' %>
<%@ Import Namespace='System.Data' %>
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<script runat='server'>
protected void Page_Load(object sender, EventArgs e)
{
SqlConnection connection = new SqlConnection('Data Source=(local);Initial Catalog=AspNetStudy;Persist Security Info=True;User ID=sa;Password=sa');
//注意,DateTime.Now.ToString('yyyy-MM-dd HH:mm:ss')是將當(dāng)前時(shí)間格式化為類似于2008-10-09 00:00:03的形式的字符串
Response.Write('時(shí)間'+ DateTime.Now.ToString('yyyy-MM-dd HH:mm:ss')+'當(dāng)前數(shù)據(jù)庫連接狀態(tài)是:'+connection.State +'<br/>');
connection.Open();
Response.Write('時(shí)間' + DateTime.Now.ToString('yyyy-MM-dd HH:mm:ss') + '當(dāng)前數(shù)據(jù)庫連接狀態(tài)是:' + connection.State + '<br/>');
connection.Close();
Response.Write('時(shí)間' + DateTime.Now.ToString('yyyy-MM-dd HH:mm:ss') + '當(dāng)前數(shù)據(jù)庫連接狀態(tài)是:' + connection.State + '<br/>');
}
</script>
<html xmlns='http://www.w3.org/1999/xhtml' >
<head runat='server'>
<title>無標(biāo)題頁</title>
</head>
<body>
<form id='form1' runat='server'>
<div>
</div>
</form>
</body>
</html>
以下是運(yùn)行效果:
Command對(duì)象
Command對(duì)象也稱為數(shù)據(jù)庫命令對(duì)象,Command對(duì)象主要執(zhí)行包括添加、刪除、修改及查詢數(shù)據(jù)的操作的命令。也可以用來執(zhí)行存儲(chǔ)過程。用于執(zhí)行存儲(chǔ)過程時(shí)需要將Command對(duì)象的CommandType 屬性設(shè)置為CommandType.StoredProcedure,默認(rèn)情況下CommandType 屬性為CommandType.Text,表示執(zhí)行的是普通SQL語句。
Command主要有三個(gè)方法:
ExecuteNonQuery () :執(zhí)行一個(gè)SQL語句,返回受影響的行數(shù),這個(gè)方法主要用于執(zhí)行對(duì)數(shù)據(jù)庫執(zhí)行增加、更新、刪除操作,注意查詢的時(shí)候不是調(diào)用這個(gè)方法。
ExecuteReader ():執(zhí)行一個(gè)查詢的SQL語句,返回一個(gè)DataReader對(duì)象。
ExecuteScalar ():從數(shù)據(jù)庫檢索單個(gè)值。這個(gè)方法主要用于統(tǒng)計(jì)操作。
有關(guān)ExecuteNonQuery ()和ExecuteReader ()的用法在稍后馬上提到,這里我主要演示ExecuteScalar ()這個(gè)方法的用法。
請(qǐng)看下面的例子:
<%@ Page Language='C#' %>
<%@ Import Namespace='System.Data.SqlClient' %>
<%@ Import Namespace='System.Data' %>
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<script runat='server'>
protected void Page_Load(object sender, EventArgs e)
{
//實(shí)例化Connection對(duì)象
SqlConnection connection = new SqlConnection('Data Source=(local);Initial Catalog=AspNetStudy;Persist Security Info=True;User ID=sa;Password=sa');
//實(shí)例化Command對(duì)象
SqlCommand command = new SqlCommand('select count(1) as 男性人數(shù) from UserInfo where sex=1', connection);
//打開Connection對(duì)象
connection.Open();
//執(zhí)行SQL語句
int count = int.Parse(command.ExecuteScalar().ToString());
//關(guān)閉Connection對(duì)象
connection.Close();
Response.Write('在UserInfo表里共有' + count + '個(gè)男性。');
}
</script>
<html xmlns='http://www.w3.org/1999/xhtml' >
<head runat='server'>
<title>無標(biāo)題頁</title>
</head>
<body>
<form id='form1' runat='server'>
<div>
</div>
</form>
</body>
</html>
這個(gè)頁面執(zhí)行的結(jié)果如下:
DataReader對(duì)象
DataReader對(duì)象是一個(gè)讀取行的只讀流的方式,綁定數(shù)據(jù)時(shí)比使用數(shù)據(jù)集方式性能要高,因?yàn)樗侵蛔x的,所以如果要對(duì)數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行修改就需要借助其它方法將所作的更改保存到數(shù)據(jù)庫。
DataReader對(duì)象不能通過直接實(shí)例化,必須借助與相關(guān)的Command對(duì)象來創(chuàng)建實(shí)例,例如用SqlCommand的實(shí)例的ExecuteReader()方法可以創(chuàng)建SqlDataReader實(shí)例。
因?yàn)镈ataReader對(duì)象讀取數(shù)據(jù)時(shí)需要與數(shù)據(jù)庫保持連接,所以在使用完DataReader對(duì)象讀取完數(shù)據(jù)之后應(yīng)該立即調(diào)用它的Close()方法關(guān)閉,并且還應(yīng)該關(guān)閉與之相關(guān)的Connection對(duì)象。在.net類庫中提供了一種方法,在關(guān)閉DataReader對(duì)象的同時(shí)自動(dòng)關(guān)閉掉與之相關(guān)的Connection對(duì)象,使用這種方法是可以為ExecuteReader()方法指定一個(gè)參數(shù),如:
SqlDataReader reader =command.ExecuteReader(CommandBehavior.CloseConnection);
CommandBehavior是一個(gè)枚舉,上面使用了CommandBehavior枚舉的CloseConnection值,它能在關(guān)閉SqlDataReader時(shí)關(guān)閉相應(yīng)的SqlConnection對(duì)象。
并且DataReader對(duì)象讀取數(shù)據(jù)有三種方式:
一種是按查詢的時(shí)候列的索引用指定的方式來讀取列值,無需做相應(yīng)轉(zhuǎn)換,如GetByte(int i)就是讀取第i列的值并且轉(zhuǎn)換成byte類型的值。第這種方法的優(yōu)點(diǎn)是指定列后直接將該列的直接讀取出來了,無需再轉(zhuǎn)換,缺點(diǎn)是一旦指定的列不能按照指定的方式轉(zhuǎn)換時(shí)就會(huì)拋出異常,比如數(shù)據(jù)庫里字段的類型是string類型或者該字段的值為空時(shí)按照GetByte(i)這種方式讀取會(huì)拋出異常。
第二種方式就是按照列索引的方式讀取,在讀取的時(shí)候并不進(jìn)行值轉(zhuǎn)換,如:reader[5]就是讀取第5列的值(這里reader是一個(gè)Reader對(duì)象的實(shí)例),這樣得到的值是一個(gè)object類型的值,這也很好理解,因?yàn)樵跀?shù)據(jù)庫可能存儲(chǔ)各種類型的值,而object是所有類的基類,所以這個(gè)方法不會(huì)拋出異常。如果要得到它的正確類型,還需要根據(jù)數(shù)據(jù)庫里的字段進(jìn)行進(jìn)行相應(yīng)轉(zhuǎn)換。
最后一種是按照列名的方式去讀,并且在讀的時(shí)候也不進(jìn)行相應(yīng)轉(zhuǎn)換,得到的是object類型的值。
綜合前面三種方式各有特點(diǎn),第一種方式最直接,但是有可能拋出異常,第二種方式比第一種稍微靈活一些,我們可以根據(jù)讀取到值為空(在.net里用DBNull類來表示,可以表示數(shù)據(jù)庫中任意數(shù)據(jù)類型的空值),我們就不進(jìn)行相應(yīng)的類型轉(zhuǎn)換,避免出現(xiàn)異常。第三種方式按照列的名字來讀取數(shù)據(jù),也需要按照第二種方式進(jìn)行一定的轉(zhuǎn)換。就性能來說第一種最高,第二種稍低,第三種最低(這很好理解,假設(shè)要在一個(gè)旅館里找人直通過房間號(hào)找肯定比通過名字找快),就靈活性來說第三種最靈活,第二種次之,第一種最不靈活(假如在后來編寫SQL語句中更改了列的索引,第一種和第二種都可能出現(xiàn)問題)。實(shí)際開發(fā)中根據(jù)實(shí)際情況選擇合適的方式。
下面是一個(gè)使用DataReader對(duì)象讀取數(shù)據(jù)的例子,因?yàn)榇a不是很多并且也不復(fù)雜,所以采用了單頁模式,并且還演示了三種讀取數(shù)據(jù)的方式,代碼如下:
<%@ Page Language='C#' %>
<%@ Import Namespace='System.Data.SqlClient' %>
<%@ Import Namespace='System.Data' %>
<%@ Import Namespace='System.Text' %>
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<script runat='server'>
//注:為了方便沒有使用代碼頁面方式,實(shí)際開發(fā)中這種做大很少見
protected void Page_Load(object sender, EventArgs e)
{
}
public void ShowData()
{
//實(shí)例化Connection對(duì)象
SqlConnection connection = new SqlConnection('Data Source=(local);Initial Catalog=AspNetStudy;Persist Security Info=True;User ID=sa;Password=sa');
//實(shí)例化Command對(duì)象
SqlCommand command = new SqlCommand('select * from UserInfo where sex=1', connection);
//打開Connection對(duì)象
connection.Open();
//得到DataReader的實(shí)例,注意使用了CommandBehavior這個(gè)參數(shù),以便同時(shí)關(guān)閉Connection
SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);
//如果當(dāng)前記錄還有下一條記錄,則循環(huán)不會(huì)終止
while (reader.Read())
{
Response.Write('<tr><td>' + reader.GetInt32(0) + '</td>');//按照列順序和對(duì)應(yīng)類型直接讀取值
Response.Write('<td>' + reader.GetString(1) + '</td>');//按照列順序和對(duì)應(yīng)類型直接讀取值
Response.Write('<td>' + reader.GetString(2) + '</td>');//按照列順序和對(duì)應(yīng)類型直接讀取值
Response.Write('<td>' + reader.GetByte(3) + '</td>');//按照列順序和對(duì)應(yīng)類型直接讀取值
//下面是按照列順序直接讀取值,并且根據(jù)值來判斷最終顯示結(jié)果
Response.Write('<td>' + (reader.GetBoolean(4)==true?'男':'女') + '</td>');
//根據(jù)列順序讀,列的值需要做相應(yīng)轉(zhuǎn)換
Response.Write('<td>' + reader[5].ToString() + '</td>');
//根據(jù)列名來讀取,列的值需要做相應(yīng)轉(zhuǎn)換
Response.Write('<td>' + reader['Phone'] + '</td>');
Response.Write('<td>' + reader['Email'].ToString() + '</td></tr>/n');
}
reader.Close();
}
</script>
<html xmlns='http://www.w3.org/1999/xhtml' >
<head runat='server'>
<title>無標(biāo)題頁</title>
</head>
<body>
<form id='form1' runat='server'>
<div>
<table border='1' cellpadding='0' cellspacing='0'>
<tr><td>編號(hào)</td><td>賬號(hào)</td><td>真實(shí)姓名</td><td>年齡</td><td>性別</td><td>手機(jī)</td><td>電話</td><td>電子郵件</td></tr>
<%
//在頁面中調(diào)用后臺(tái)代碼,這樣也能保證生成的代碼不會(huì)位于<html></html>標(biāo)記之外
ShowData();
%>
</table>
</div>
</form>
</body>
</html>
下面是運(yùn)行結(jié)果:
DataAdapter對(duì)象
DataAdapter對(duì)象也稱之為數(shù)據(jù)適配器對(duì)象,DataAdapter對(duì)象利用數(shù)據(jù)庫連接對(duì)象(Connection)連接的數(shù)據(jù)源,使用數(shù)據(jù)庫命令對(duì)象(Command)規(guī)定的操作從數(shù)據(jù)源中檢索出數(shù)據(jù)送往數(shù)據(jù)集對(duì)象(DataSet),或者將數(shù)據(jù)集中經(jīng)過編輯后的數(shù)據(jù)送回?cái)?shù)據(jù)源。
數(shù)據(jù)適配器將數(shù)據(jù)填入數(shù)據(jù)集時(shí)調(diào)用方法Fill(),語句如下:
dataAdapter1.Fill (dataTable);//直接填充表
或者
dataAdapter1.Fill (dataSet11, 'Products');//填充dataSet11數(shù)據(jù)集中的'Products'表
當(dāng)dataAdapter1調(diào)用Fill() 方法時(shí)將使用與之相關(guān)聯(lián)的命令組件所指定的 SELECT 語句從數(shù)據(jù)源中檢索行。然后將行中的數(shù)據(jù)添加到 DataSet 中的DataTable 對(duì)象中或者直接填充到DataTable的實(shí)例中,如果 DataTable 對(duì)象不存在,則自動(dòng)創(chuàng)建該對(duì)象。
當(dāng)執(zhí)行上述SELECT語句時(shí),與數(shù)據(jù)庫的連接必須有效,但不需要用語句將連接對(duì)象打開。如果調(diào)用Fill()方法之前與數(shù)據(jù)庫的連接已經(jīng)關(guān)閉,則將自動(dòng)打開它以檢索數(shù)據(jù),執(zhí)行完畢后再自動(dòng)將其關(guān)閉。如果調(diào)用Fill()方法之前連接對(duì)象已經(jīng)打開,則檢索后繼續(xù)保持打開狀態(tài)。
注意:一個(gè)數(shù)據(jù)集中可以放置多張數(shù)據(jù)表。但是每個(gè)數(shù)據(jù)適配器只能夠?qū)?yīng)于一張數(shù)據(jù)表。
DataSet對(duì)象
DataSet對(duì)象也稱為數(shù)據(jù)集對(duì)象,DataSet對(duì)象用于表示那些儲(chǔ)存在內(nèi)存中的數(shù)據(jù),它相當(dāng)于一個(gè)內(nèi)存中的數(shù)據(jù)庫。它可以包括多個(gè)DataTable對(duì)象及DataView對(duì)象。DataSet主要用于管理存儲(chǔ)在內(nèi)存中的數(shù)據(jù)以及對(duì)數(shù)據(jù)的斷開操作。
由于DataSet對(duì)象提供了一個(gè)離線的數(shù)據(jù)源,這樣減輕了數(shù)據(jù)庫以及網(wǎng)絡(luò)的負(fù)擔(dān),在設(shè)計(jì)程序的時(shí)候可以將DataSet對(duì)象作為程序的數(shù)據(jù)源。
下面是一個(gè)利用Adapter對(duì)象填充數(shù)據(jù)到DataTable(或DataSet)并顯示的例子,這個(gè)例子采用了頁面和代碼分離模式,前臺(tái)代碼如下:
<%@ Page Language='C#' AutoEventWireup='true' CodeFile='DataAdapter.aspx.cs' Inherits='DataAdapter' %>
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml' >
<head runat='server'>
<title>利用Adapter對(duì)象填充DataTable的例子</title>
</head>
<body>
<form id='form1' runat='server'>
<div>
<table border='1' cellpadding='0' cellspacing='0'>
<tr><td>編號(hào)</td><td>賬號(hào)</td><td>真實(shí)姓名</td><td>年齡</td><td>性別</td><td>手機(jī)</td><td>電話</td><td>電子郵件</td></tr>
<%
//在頁面中調(diào)用后臺(tái)代碼,這樣也能保證生成的代碼不會(huì)位于<html></html>標(biāo)記之外
ShowData();
%>
</table>
</div>
</form>
</body>
</html>
相應(yīng)的后臺(tái)代碼:
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Data.SqlClient;
public partial class DataAdapter : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
//供前臺(tái)頁面調(diào)用的方法,這個(gè)方法必須是protected或者public
protected void ShowData()
{
//實(shí)例化Connection對(duì)象
SqlConnection connection = new SqlConnection('Data Source=(local);Initial Catalog=AspNetStudy;Persist Security Info=True;User ID=sa;Password=sa');
//實(shí)例化Command對(duì)象
SqlCommand command = new SqlCommand('select * from UserInfo where sex=0', connection);
SqlDataAdapter adapter = new SqlDataAdapter(command);
/*
下面的被注釋掉的代碼與上面的代碼是等效的
SqlDataAdapter adapter = new SqlDataAdapter('select * from UserInfo where sex=0', connection);
*/
DataTable data = new DataTable();
adapter.Fill(data);
/* 下面的被注釋掉語句與上面填充DataTable的效果是一樣的,我更傾向于沒有注釋掉的部分
DataSet ds = new DataSet();//實(shí)例化DataSet
adapter.Fill(ds, 'UserInfo');//填充ds中的'UserInfo'表
DataTable data = ds.Tables['UserInfo'];
*/
for (int i = 0; i < data.Rows.Count; i++)
{
Response.Write('<tr><td>' + data.Rows[i]['UserId'].ToString() + '</td>');
Response.Write('<td>' + data.Rows[i]['UserName'].ToString() + '</td>');
Response.Write('<td>' + data.Rows[i]['RealName'].ToString() + '</td>');
Response.Write('<td>' + data.Rows[i]['Age'].ToString() + '</td>');
//下面是按照列順序直接讀取值,并且根據(jù)值來判斷最終顯示結(jié)果
Response.Write('<td>' + (bool.Parse(data.Rows[i]['Sex'].ToString()) == true ? '男' : '女') + '</td>');
//根據(jù)列順序讀,列的值需要做相應(yīng)轉(zhuǎn)換
Response.Write('<td>' + data.Rows[i]['Mobile'].ToString() + '</td>');
//根據(jù)列名來讀取,列的值需要做相應(yīng)轉(zhuǎn)換
Response.Write('<td>' + data.Rows[i]['Phone'].ToString() + '</td>');
Response.Write('<td>' + data.Rows[i]['Email'].ToString() + '</td></tr>/n');
}
}
}
上面的程序代碼基本與使用DataReader對(duì)象的例子相同,不過這里查詢的所有女性用戶,效果如下:
DataTable對(duì)象
DataTable 是 ADO.NET 庫中的核心對(duì)象,就像普通的數(shù)據(jù)庫中的表一樣,它也有行和列。它主要包括DataRow和DataColumn,分別代表行和列。
(1) 數(shù)據(jù)行(DataRow)
數(shù)據(jù)行是給定數(shù)據(jù)表中的一行數(shù)據(jù),或者說是數(shù)據(jù)表中的一條記錄。它可能代表一個(gè)學(xué)生、一位用戶、一張訂單或者一件貨物的相關(guān)數(shù)據(jù)。DataRow對(duì)象的方法提供了對(duì)表中數(shù)據(jù)的插入、刪除、更新和查看等功能。提取數(shù)據(jù)表中的行的語句如下:
DataRow dr = dt.Rows[n];
其中:DataRow代表數(shù)據(jù)行類;dr是數(shù)據(jù)行對(duì)象;dt代表數(shù)據(jù)表對(duì)象; n代表行的序號(hào)(序號(hào)從0開始)。
(2) 數(shù)據(jù)列(DataColumn)
數(shù)據(jù)表中的數(shù)據(jù)列(又稱字段)定義了表的數(shù)據(jù)結(jié)構(gòu),例如,可以用它確定列中的數(shù)據(jù)類型和大小,還可以對(duì)其他屬性進(jìn)行設(shè)置。例如,確定列中的數(shù)據(jù)是否是只讀的、是否是主鍵、是否允許空值等;還可以讓列在一個(gè)初始值的基礎(chǔ)上自動(dòng)增殖,增值的步長還可以自行定義。
某列的值需要在數(shù)據(jù)行的基礎(chǔ)上進(jìn)行。語句如下:
string dc = dr.Columns['字段名'].ToString();
或者
string dc = dr.Column[i].ToString();//i表示對(duì)應(yīng)的列索引
綜合前面的語句,若想取出數(shù)據(jù)表(dt)中第3條記錄中的“姓名”字段,并將該字段的值放入一輸入框(textBox1)中時(shí),語句可以寫成:
DataRow dRow = dt.Rows[2 ]; // 從數(shù)據(jù)表提取行
string textBox1.Text=dRow['CompanyName'].ToString(); // 從行中取出字段的值
下面是一個(gè)利用代碼在內(nèi)存中創(chuàng)建表并顯示的例子(仍然是采用了單頁模式):
<%@ Page Language='C#' %>
<%@ Import Namespace='System.Data' %>
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<script runat='server'>
protected void Page_Load(object sender, EventArgs e)
{
}
private void CreateDataTable()
{
//實(shí)例化DataTable
DataTable data = new DataTable();
//創(chuàng)建一個(gè)名為'ID'的列,值類型為int
DataColumn dc1 = new DataColumn('ID', typeof(int));
dc1.AllowDBNull = false;//不允許為空
dc1.AutoIncrement = true;//自動(dòng)遞增
dc1.AutoIncrementSeed = 1;//列起始值為1
dc1.AutoIncrementStep = 1;//步長為1
data.Columns.Add(dc1);//添加列到表中
//創(chuàng)建一個(gè)新列,列名為'UserName',值類型為string
DataColumn dc = new DataColumn('UserName', typeof(string));
dc.Unique = true;//設(shè)置唯一索引
dc.MaxLength = 20;//設(shè)置字段最大長度
data.Columns.Add(dc);
dc = new DataColumn('Birthday', typeof(DateTime));
dc.DefaultValue = DateTime.Now.AddYears(-2000);
data.Columns.Add(dc);
DataRow row=data.NewRow();//得到與剛才創(chuàng)建的表有相同結(jié)構(gòu)的行
row['UserName']='張飛';//設(shè)置列的UserName值,Birthday列采用默認(rèn)值
data.Rows.Add(row);//添加行
row = data.NewRow();
row['UserName'] = '劉備';
row['Birthday'] = new DateTime(1, 3, 4);
data.Rows.Add(row);
row = data.NewRow();
row['UserName'] = '關(guān)羽';
row['Birthday'] = new DateTime(6, 11, 7);//跟周公同月同日了:)
data.Rows.Add(row);
Session['Data'] = data;//將創(chuàng)建的表和添加的數(shù)據(jù)保存到Session中
}
protected void ShowData()
{
if (Session['Data'] == null)
{
CreateDataTable();
}
DataTable data=(DataTable)Session['Data'];
for (int i = 0; i < data.Rows.Count; i++)
{
Response.Write('<tr>');
Response.Write('<td>' + data.Rows[i]['ID'].ToString() + '</td>');
Response.Write('<td>' + data.Rows[i]['UserName'].ToString() + '</td>');
Response.Write('<td>' + DateTime.Parse(data.Rows[i]['Birthday'].ToString()).ToShortDateString() + '</td>');
Response.Write('</tr>');
}
}
</script>
<html xmlns='http://www.w3.org/1999/xhtml' >
<head runat='server'>
<title>自己創(chuàng)建DataTable的例子</title>
</head>
<body>
<form id='form1' runat='server'>
<div>
<table border='1' width='400'>
<tr><td>編號(hào)</td><td>用戶名</td><td>生日</td></tr>
<% ShowData(); %>
</table>
</div>
</form>
</body>
</html>
上面代碼我已經(jīng)做了詳盡的注釋,它的運(yùn)行效果如下:
參數(shù)化SQL語句
在前面的系列文章《asp.net夜話之三:表單和控件》中我提到了SQL注入的問題,避免SQL注入的方法有兩種:一是所有的SQL語句都存放在存儲(chǔ)過程中,這樣不但可以避免SQL注入,還能提高一些性能,并且存儲(chǔ)過程可以由專門的數(shù)據(jù)庫管理員(DBA)編寫和集中管理(這種做法我在一些公司見過),不過這種做法有時(shí)候針對(duì)相同的幾個(gè)表有不同條件的查詢,SQL語句可能不同,這樣就會(huì)編寫大量的存儲(chǔ)過程,所以有人提出了第二種方案:參數(shù)化SQL語句。例如我們?cè)诒酒袆?chuàng)建的表UserInfo中查找所有女性用戶,那么通常情況下我們的SQL語句可能是這樣:
select * from UserInfo where sex=0
在參數(shù)化SQL語句中我們將數(shù)值以參數(shù)化的形式提供,對(duì)于上面的查詢,我們用參數(shù)化SQL語句表示為:
select * from UserInfo where sex=@sex
我們?cè)賹?duì)代碼中對(duì)這個(gè)SQL語句中的參數(shù)進(jìn)行賦值,假如我們要查找UserInfo表中所有年齡大于30歲的男性用戶,這個(gè)參數(shù)化SQL語句可以這么寫:
select * from UserInfo where sex=@sex and age>@age
下面是執(zhí)行這個(gè)查詢并且將查詢結(jié)果集以DataTable的方式返回的代碼:
//實(shí)例化Connection對(duì)象
SqlConnection connection = new SqlConnection('Data Source=(local);Initial Catalog=AspNetStudy;Persist Security Info=True;User ID=sa;Password=sa');
//實(shí)例化Command對(duì)象
SqlCommand command = new SqlCommand('select * from UserInfo where sex=@sex and age>@age', connection);
//第一種添加查詢參數(shù)的例子
command.Parameters.AddWithValue('@sex', true);
//第二種添加查詢參數(shù)的例子
SqlParameter parameter = new SqlParameter('@age', SqlDbType.Int);//注意UserInfo表里age字段是int類型的
parameter.Value = 30;
command.Parameters.Add(parameter);//添加參數(shù)
//實(shí)例化DataAdapter
SqlDataAdapter adapter = new SqlDataAdapter(command);
DataTable data = new DataTable();
上面的代碼是訪問SQL Server數(shù)據(jù)庫的代碼。如果本文中提到的數(shù)據(jù)分別在Access、MySQL、Oracle數(shù)據(jù)庫,那么對(duì)應(yīng)的參數(shù)化SQL語句及參數(shù)分別如下:
數(shù)據(jù)庫 | Access | MySQL | Oracle |
SQL 語句 | select * from UserInfo where sex=? and age>? | select * from UserInfo where sex=?sex and age>?age | select * from UserInfo where sex=:sex and age>:age |
參數(shù) | OleDbParameter | MySqlParameter | OracleParameter |
實(shí)例 化參 數(shù) | OleDbParameter p=new OleDbParameter(“?”, OleDbType. Boolean); | MySqlParameter p=new MySqlParameter(“?sex”, MySqlDbType.Bit); | OracleParameter p=new OracleParameter(“:sex”, OracleType.Byte); |
賦值 | p.Value=true; | p.Value=1; | p.Value=1; |
分頁查詢SQL語句
在實(shí)際中我們經(jīng)常遇到表里的記錄數(shù)非常龐大(數(shù)萬至數(shù)百萬),而一次只顯示幾十條數(shù)據(jù)的情況,如果我們直接用下面的SQL語句查詢并填充到DataTable的話,將是一個(gè)非??植赖氖虑?假設(shè)UserInfo有幾百萬用戶數(shù)據(jù)):
Select * from UserInfo
在csdn論壇里我曾經(jīng)就見過有一位朋友一張表里有8萬條左右的記錄,他每次都是將全部數(shù)據(jù)填充到DataTable,然后在循環(huán)中決定顯示哪些數(shù)據(jù),最后他在論壇上求助,說那樣做的效果太慢了,當(dāng)時(shí)把我和其它幾個(gè)回帖的朋友嚇懵了。其實(shí)他這種情況就應(yīng)該用分頁查詢。
分頁查詢就是根據(jù)需要每次只返回所需要的數(shù)據(jù),而不用每次都從數(shù)據(jù)庫中全部把數(shù)據(jù)提取出來,這樣可以降低程序與數(shù)據(jù)庫之間的數(shù)據(jù)傳送量,并且還可以提高程序的性能。
一般來說我們?cè)跀?shù)據(jù)量大的情況下總是會(huì)分頁顯示(誰也不會(huì)一下子將幾萬條數(shù)據(jù)全部一次性顯示給用戶),這樣決定我們返回的查詢結(jié)果集的參數(shù)有兩個(gè):當(dāng)前顯示的頁數(shù)pageIndex和每頁顯示的記錄條數(shù)size。
這里來舉例在SQL Server中我們?nèi)绾螌懛猪摬樵兊腟QL語句,假設(shè)我們按照UserID字段降序查詢,每頁顯示5條記錄。
下面是按照UserID字段降序查詢?nèi)克杏脩舻慕貓D(出于某些原因,我把前面的14條記錄刪除掉了):
//實(shí)例化Connection對(duì)象
SqlConnection connection = new SqlConnection('Data Source=(local);Initial Catalog=AspNetStudy;Persist Security Info=True;User ID=sa;Password=sa');
//實(shí)例化Command對(duì)象
SqlCommand command = new SqlCommand('select count(1) as 男性人數(shù) from UserInfo where sex=1', connection);
//打開Connection對(duì)象
connection.Open();
//執(zhí)行SQL語句
//得到第一行第一列的結(jié)果,這里是所有用戶總數(shù)
int count = int.Parse(command.ExecuteScalar().ToString());
//關(guān)閉Connection對(duì)象
connection.Close();
最后一點(diǎn),如果計(jì)算總頁數(shù)的問題。假如我們有20條記錄,每頁顯示5條,毫無疑問總共分4頁顯示。如果是21條記錄呢?答案是分5頁顯示,盡管最后一頁只有一條記錄,但是還是要顯示的。這里也有一個(gè)公式,假如總共有m條記錄,每頁顯示n條記錄(這里m,n都是大于0的整數(shù))那么需要顯示全部記錄所用到的頁數(shù)page為:
page=(m%n)==0?(m/n):(m/n+1);
題外話:今夜的夜話就到這里了,原來計(jì)劃寫一個(gè)數(shù)據(jù)庫訪問通用類的(這個(gè)很多朋友經(jīng)常問到),但是這篇文章的篇幅確實(shí)太長了,足足25頁Word文檔,我怕csdn博客不能放下這個(gè)長的內(nèi)容,如果確實(shí)有人要我再補(bǔ)上。下一篇我將講述數(shù)據(jù)綁定的有關(guān)知識(shí)。ADO.NET和數(shù)據(jù)綁定是.net編程中非常重要的兩部分。下一篇我打算花更長一點(diǎn)的時(shí)間醞釀寫作思路,希望給大家?guī)砀髱椭?br>周公(周金橋)
2008-10-13 01:17
注意,因?yàn)閭€(gè)人空間大小和下載速度受限,所以以后不再提供從本人主機(jī)上的下載地址,可以到www.verycd.com下載《ASP.NET夜話》的測試版視頻教程。地址是:http://www.verycd.com/topics/2730883/
聯(lián)系客服