ado.net詳細(xì)研究 —— DataReader
這次我們?cè)敿?xì)研究DataReader。我個(gè)人最喜歡的就是DataReader,雖然它不如DataSet強(qiáng)大,但是在很多情況下我們須要的是靈活的讀取數(shù)據(jù)而不是大量的在內(nèi)存里面緩存數(shù)據(jù)。比如在網(wǎng)絡(luò)上每個(gè)用戶都緩存大量的dataset,這很可能導(dǎo)致服務(wù)器內(nèi)存不足。另外dataReader尤其適合讀取大量的數(shù)據(jù),因?yàn)樗辉趦?nèi)存中緩存數(shù)據(jù)。
由于下面的討論都設(shè)計(jì)到數(shù)據(jù)庫操作,我們虛擬一個(gè)小項(xiàng)目:個(gè)人通訊錄(單用戶),這意味著我們須要一個(gè)contract的數(shù)據(jù)庫,包含admin和fridend:
admin :Aname,Apassword
friend :Fname,Fphone,Faddress,Fid(主鍵)
當(dāng)然,你可以根據(jù)自己的須要設(shè)計(jì)friend表,比如添加Fsex等字段,這里不詳細(xì)列舉。對(duì)應(yīng)數(shù)據(jù)庫建立文件:
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,雖然前面我們已經(jīng)簡(jiǎn)短的介紹過了。
以下的所有討論都針對(duì)Sql Server2000,使用的命名空間為System.Data.SqlClient。當(dāng)然如果你須要使用OleDb,那是很方便的(基本上是把Sql替換成為OleDb就可以了)。
1, SqlConnection類
連接Sql server首先必須實(shí)例化一個(gè)SqlConnection對(duì)象:
SqlConnection Conn = new SqlConnection(ConnectionString);
Conn.Open();
或者
SqlConnection Conn = new SqlConnection();
Conn.ConnectionString = ConnectionString;
Conn.Open();
我比較喜歡前者,但是當(dāng)你須要重新使用Connection對(duì)象去連接另外的數(shù)據(jù)庫的時(shí)候,第二種方法非常有效(不過這種機(jī)會(huì)很少,一般來說一個(gè)小型系統(tǒng)只對(duì)應(yīng)一個(gè)數(shù)據(jù)庫——個(gè)人認(rèn)為^_^)。
SqlConnection Conn = new SqlConnection();
Conn.ConnectionString = ConnectionString1;
Conn.Open();
//do something
Conn,Close();
Conn.ConnectionString = ConnectionString2;
Conn.Open();
//do something else
Conn,Close();
注意只有關(guān)閉一個(gè)連接以后才能使用另外的連接。
如果你不清楚Connection對(duì)象的狀態(tài),可以使用State屬性,它的值為Open或者Closed,當(dāng)然也還有其他值如Executing或者Broken,但是Sql server等當(dāng)前版本都不支持。
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
連接上數(shù)據(jù)庫以后一定記得關(guān)閉連接,在ado.net中當(dāng)Connection對(duì)象超出范圍時(shí)連接不會(huì)自動(dòng)關(guān)閉。
打開數(shù)據(jù)庫連接以后我們要執(zhí)行命令,所以我們討論一下Command類
2 SqlCommand類
建立數(shù)據(jù)庫連接以后我們須要訪問和操作數(shù)據(jù)庫——CRUD:Create、Read、Update、Delete。
為了執(zhí)行命令我們創(chuàng)建Command對(duì)象,Comand對(duì)象要求執(zhí)行Connection對(duì)象和CommandText對(duì)象。
SqlCommand cmd = new SqlCommand();
cmd.Connection = ConnectionObject;//比如我們先前建立的Conn對(duì)象
cmd.CommandText = CommandText;//比如一個(gè)Select語句
string CommandText = “Select * from friend”;
當(dāng)然我們也可以使用存儲(chǔ)過程,這個(gè)以后討論。
另外的方法:
SqlCommand cmd = new SqlCommand(CommandText);
cmd.Connection = ConnectionObject;//比如我們先前建立的Conn對(duì)象
或者
SqlCommand cmd = new SqlCommand(CommandText,ConnecionObject);
另外還有一個(gè)包含三個(gè)參數(shù)的構(gòu)造函數(shù),我們不討論。設(shè)計(jì)到的是事務(wù)處理。
有了Command對(duì)象以后我們須要執(zhí)行操作,但是執(zhí)行前請(qǐng)一定記得打開你的數(shù)據(jù)庫連接,否則會(huì)有異常。SqlCommand對(duì)象提供下面4個(gè)執(zhí)行方法:
l ExecuteNonQuery
l ExecuteScalar
l ExecuteReader
l ExecuteXmlReader
ExecuteNonQuery方法執(zhí)行不返回結(jié)果的命令,通常使用它執(zhí)行插入、刪除、更新操作。
例如我們對(duì)contract數(shù)據(jù)庫進(jìn)行操作:
string sqlIns = “insert [friend] (Fname,Fphone,Faddress) values (‘雪冬寒’,’027-87345555’,’武漢大學(xué)宏博公寓’); cmd.CommandText = sqlIns; cmd.ExecuteNonQuery(); string sqlUpdate = “update [frined] set Faddress = ‘武漢大學(xué)’ where Fid = 1”; cmd.CommandText = sqlUpdate; cmd.ExecuteNonQuery(); string sqlDel = “delete from [friend] where Fid = 1; cmd.CommandText = sqlDel; cmd.ExecuteNonQuery();
|
注:如果你要測(cè)試以上代碼,請(qǐng)自己書寫,不要復(fù)制和粘貼,這樣會(huì)存在文字錯(cuò)誤(漢語和英語的符號(hào)問題)。
ExecuteScalar方法執(zhí)行返回單個(gè)值的命令,例如我們須要統(tǒng)計(jì)系統(tǒng)中所有聯(lián)系人的數(shù)量,就可以這樣:
SqlCommand cmd = new SqlCommand(“select count(*) from friend”, Conn); Conn.open(); int friendCount = (int) cmd.ExecuteScalar(); MessageBox.Show(“共” + friendCount.ToString() + “個(gè)聯(lián)系人”);
|
說明:命令可以返回多個(gè)結(jié)果,此時(shí)ExecuteScalar方法將返回第一行第一個(gè)字段的值,同時(shí)其他所有值不可訪問,這意味著如果象獲得最好的性能,您應(yīng)該構(gòu)造適當(dāng)?shù)?/span>select查詢,以便查詢的結(jié)果集中盡可能少的包含額外的數(shù)據(jù)。如果只對(duì)單個(gè)返回值感興趣,這個(gè)方法是首選的方法。另外該方法返回object類型數(shù)據(jù),所有保證進(jìn)行適當(dāng)?shù)念愋娃D(zhuǎn)換是您的責(zé)任,否則您將得到異常。
ExecuteXmlReader方法執(zhí)行返回一個(gè)xml字符串的命令。它將返回一個(gè)包含所返回的sml的System.Xml.XmlReader對(duì)象。這個(gè)方法我一無所知,不作討論^_^。
DataReader類
1. 創(chuàng)建DataReader對(duì)象
前面提到過沒有構(gòu)造函數(shù)創(chuàng)建DataReader對(duì)象。通常我們使用Command類的ExecuteRader方法來創(chuàng)建DataReader對(duì)象:
SqlCommand cmd = new SqlCommand(commandText,ConnectionObject) SqlDataReader dr = cmd.ExecuteReader();
|
DataReader類最常見的用法就是檢索Sql查詢或者存儲(chǔ)過程返回的記錄。它是連接的只向前和只讀的結(jié)果集,也就是使用它時(shí),數(shù)據(jù)庫連接必須保持打開狀態(tài),另外只能從前往后遍歷信息,不能中途停下修改數(shù)據(jù)。
注意:DataReader使用底層的連接,連接是它專有的,這意味這DataReader打開時(shí)不能使用對(duì)應(yīng)連接進(jìn)行去他操作,比如執(zhí)行另外的命令等。使用完DataReader后一定記得關(guān)閉閱讀器和連接。
2. 使用命令行為指定DataReader的特征
前面我們使用cmd.ExecuteReader()實(shí)例化DataReader對(duì)象,其實(shí)這個(gè)方法有重載版本,接受命令行參數(shù),這些參數(shù)應(yīng)該時(shí)Commandbehavior枚舉:
SqlDataRader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
上面我們使用的是CommandBehavior.CloseConnection,作用是關(guān)閉DataReader的時(shí)候自動(dòng)關(guān)閉對(duì)應(yīng)的ConnectionObject。這樣可以避免我們忘記關(guān)閉DataReader對(duì)象以后關(guān)閉Connection對(duì)象。別告訴我你不喜歡這個(gè)參數(shù),你能保證你記得關(guān)閉連接。萬一你忘記了呢?又或者你使用你的partner開發(fā)的組件來進(jìn)行開發(fā)呢?這個(gè)組件并不一定讓你有關(guān)閉連接的權(quán)限哦。另外CommandBehavior.SingleRow可以使結(jié)果集返回單個(gè)行,CommandBehavior.SingleResult返回結(jié)果為多個(gè)結(jié)果集的第一個(gè)結(jié)果集。當(dāng)然Commandbehavior枚舉還有其他值,請(qǐng)參見msdn。
3. 遍歷DataReader中的記錄
當(dāng)ExecuteReader方法分會(huì)DataReader對(duì)象時(shí),當(dāng)前光標(biāo)的位置時(shí)第一條記錄的前面。必須調(diào)用數(shù)據(jù)閱讀器的Read方法把光標(biāo)移動(dòng)到第一條記錄,然后第一條記錄就是當(dāng)前記錄。如果閱讀器包含的記錄不止一條,Read方法返回一個(gè)bool值true。也就是說Read方法的作用是在允許范圍內(nèi)移動(dòng)光標(biāo)位置到下一記錄,有點(diǎn)類似rs.movenext,不是嗎?如果當(dāng)前光標(biāo)指示著最后一條記錄,此時(shí)調(diào)用Read方法得到false。我們經(jīng)常這樣做:
While(dr.Reader())
{
//do something with the current record
}
注意,如果你對(duì)每一條記錄的操作可能花費(fèi)比較長(zhǎng)的時(shí)間,那么意味著閱讀器將長(zhǎng)時(shí)間打開,那么數(shù)據(jù)庫連接也將維持長(zhǎng)時(shí)間的打開狀態(tài)。此時(shí)使用非連接的DataSet或許更好一些。
4. 訪問字段的值
有2種方法。第一種是Item屬性,此屬性返回字段索引或者字段名字對(duì)應(yīng)的字段的值。第二種是Get方法,此方法返回有字段索引指定的字段的值。有點(diǎn)難以理解,不是嗎?不要緊,看例子就OK了。
Item屬性
每個(gè)DataReader類都定義一個(gè)Item屬性。比如現(xiàn)在我們有一個(gè)DataReader實(shí)例dr,對(duì)應(yīng)的sql語句是select Fid,Fname from friend,則我們可以使用下面的方法取得返回的值:
object ID = dr[“Fid”]; object Name = dr[“Fname”]; 或者: object ID = dr[0]; object Name = dr[0];
|
注意索引總是從0開始的。另外也許您發(fā)現(xiàn)了我們使用的是object來定義對(duì)ID和Name,是的,Item屬性返回的值是object型,但是您可以強(qiáng)制類型轉(zhuǎn)換。
int ID = (int)dr[“Fid”];
string Name = (string)dr[“Fname”];
記住:確保類型轉(zhuǎn)換的有效性是您自己的責(zé)任,否則您將得到異常。
Get方法
起始我們?cè)诘谝黄恼吕锩嬉呀?jīng)使用過改方法了。每個(gè)DataReader都定義了一組Get方法,比如GetInt32方法把返回的字段值作為.net clr 32位證書。同上面的例子一樣我們用如下方式訪問Fid和Fname的值:
int ID = dr.GetInt32(0); string Name = dr.GetString(1);
|
注意雖然這些方法把數(shù)據(jù)從數(shù)據(jù)源類型轉(zhuǎn)化為.net數(shù)據(jù)類型,但是他們不執(zhí)行其他的數(shù)據(jù)轉(zhuǎn)換,比如他們不會(huì)把16位整數(shù)轉(zhuǎn)換為32位的。所以您必須使用正確的Get方法。另外Get方法不能使用字段名來訪問字段,也就是說上面的沒有:
int ID = dr.GetInt32(“Fid”); //錯(cuò)誤
string Name = dr.GetString(“Fname”); //錯(cuò)誤
顯然上面這個(gè)缺點(diǎn)在某些場(chǎng)合是致命的,當(dāng)你的字段很多的時(shí)候,或者說你過了一段時(shí)間以后再來看你這些代碼,你會(huì)覺得很難以理解!當(dāng)然我們可以使用其他方法來盡量解決這個(gè)問題。一個(gè)可行的辦法是使用const:
const int FidIndex = 0;
const int NameIndex = 1;
int ID = dr.GetInt32(FidIndex);
string Name = dr.GetString(NameIndex);
這個(gè)辦法并不怎么好,另外一個(gè)好一些的辦法:
int NameIndex = dr.GetOrdinal(“Fname”); //取得Fname對(duì)應(yīng)的索引值
string Name = dr.GetString(NameIndex);
這樣似乎有點(diǎn)麻煩,但是當(dāng)須要遍歷閱讀器種大量的結(jié)果集的時(shí)候,這個(gè)方法很有效,因?yàn)樗饕恍鑸?zhí)行一次。
int FidIndex = dr.GetOrdinal(“Fid”);
int NameIndex = dr.GetOrdinal(“Fname”);
while(dr.Read())
{
int ID = dr.GetInt32(FidIndex);
string Name = dr.GetInt32(NameIndex);
}
到目前為止,我們已經(jīng)討論了DataReader的基本操作了