ado.net詳細研究 —— DataReader

這次我們詳細研究DataReader。我個人最喜歡的就是DataReader,雖然它不如DataSet強大,但是在很多情況下我們須要的是靈活的讀取數據而不是大量的在內存里面緩存數據。比如在網絡上每個用戶都緩存大量的dataset,這很可能導致服務器內存不足。另外dataReader尤其適合讀取大量的數據,因為它不在內存中緩存數據。
由于下面的討論都設計到數據庫操作,我們虛擬一個小項目:個人通訊錄(單用戶),這意味著我們須要一個contract的數據庫,包含adminfridend
admin
AnameApassword
friend
FnameFphoneFaddressFid(主鍵)
當然,你可以根據自己的須要設計friend表,比如添加Fsex等字段,這里不詳細列舉。對應數據庫建立文件:

 

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[admin]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[admin]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[friend]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[friend]
GO
CREATE TABLE [dbo].[admin] (
[Aname] [varchar] (8) COLLATE Chinese_PRC_CI_AS NOT NULL ,
[Apassword] [varchar] (16) COLLATE Chinese_PRC_CI_AS NOT NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[friend] (
[Fid] [int] IDENTITY (1, 1) NOT NULL ,
[Fname] [varchar] (8) COLLATE Chinese_PRC_CI_AS NOT NULL ,
[Fphone] [varchar] (12) COLLATE Chinese_PRC_CI_AS NULL ,
[Faddress] [varchar] (100) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO

在討論DataReader之前我們必須了解ConnectionCommand,雖然前面我們已經簡短的介紹過了。
以下的所有討論都針對Sql Server2000,使用的命名空間為System.Data.SqlClient。當然如果你須要使用OleDb,那是很方便的(基本上是把Sql替換成為OleDb就可以了)。
1
SqlConnection
連接Sql server首先必須實例化一個SqlConnection對象:
SqlConnection Conn = new SqlConnection(ConnectionString);
Conn.Open();


或者

SqlConnection Conn = new SqlConnection()

Conn.ConnectionString = ConnectionString;
Conn.Open();

我比較喜歡前者,但是當你須要重新使用Connection對象去連接另外的數據庫的時候,第二種方法非常有效(不過這種機會很少,一般來說一個小型系統只對應一個數據庫——個人認為^_^)。
SqlConnection Conn = new SqlConnection()

Conn.ConnectionString = ConnectionString1;
Conn.Open();
//do something
Conn,Close();
Conn.ConnectionString = ConnectionString2;
Conn.Open();
//do something else
Conn,Close();
注意只有關閉一個連接以后才能使用另外的連接。
如果你不清楚Connection對象的狀態,可以使用State屬性,它的值為Open或者Closed,當然也還有其他值如Executing或者Broken,但是Sql server等當前版本都不支持。
If(Conn.State == ConnectionState.Open)
Conn.Colse();
上面一直提到ConnectionString,一般連接sql server的字符串為:
Data source = serverName;Initial catalog =contract;user id =sa;password= yourpassword;
如果你的sql server使用的是windows集成密碼,則是:
Data source = serverName;Initial catalog = contract;Integrated Security
SSPI;
至于其他的oledb或者odbc連接串可以到http://www.connectionstrings.com
連接上數據庫以后一定記得關閉連接,在ado.net中當Connection對象超出范圍時連接不會自動關閉。
打開數據庫連接以后我們要執行命令,所以我們討論一下Command
2 SqlCommand

建立數據庫連接以后我們須要訪問和操作數據庫——CRUDCreateReadUpdateDelete
為了執行命令我們創建Command對象,Comand對象要求執行Connection對象和CommandText對象。
SqlCommand cmd
new SqlCommand();
cmd.Connection = ConnectionObject;//
比如我們先前建立的Conn對象
cmd.CommandText = CommandText;//
比如一個Select語句
string CommandText
“Select * from friend”
當然我們也可以使用存儲過程,這個以后討論。
另外的方法:
SqlCommand cmd = new SqlCommand(CommandText);
cmd.Connection = ConnectionObject;//
比如我們先前建立的Conn對象

或者

SqlCommand cmd
new SqlCommandCommandTextConnecionObject);
另外還有一個包含三個參數的構造函數,我們不討論。設計到的是事務處理。
有了Command對象以后我們須要執行操作,但是執行前請一定記得打開你的數據庫連接,否則會有異常。SqlCommand對象提供下面4個執行方法:
l ExecuteNonQuery
l ExecuteScalar
l ExecuteReader
l ExecuteXmlReader

ExecuteNonQuery
方法執行不返回結果的命令,通常使用它執行插入、刪除、更新操作。

例如我們對contract數據庫進行操作:

 

string sqlIns = “insert [friend] (Fname,Fphone,Faddress) values (‘雪冬寒’,’027-87345555’,’武漢大學宏博公寓’);
cmd.CommandText = sqlIns;
cmd.ExecuteNonQuery();
string sqlUpdate = “update [frined] set Faddress = ‘
武漢大學’ where Fid = 1”;
cmd.CommandText = sqlUpdate;
cmd.ExecuteNonQuery();
string sqlDel = “delete from [friend] where Fid = 1;
cmd.CommandText = sqlDel;
cmd.ExecuteNonQuery();

注:如果你要測試以上代碼,請自己書寫,不要復制和粘貼,這樣會存在文字錯誤(漢語和英語的符號問題)。

ExecuteScalar
方法執行返回單個值的命令,例如我們須要統計系統中所有聯系人的數量,就可以這樣:

 

SqlCommand cmd = new SqlCommand(“select count(*) from friend”, Conn);
Conn.open();
int friendCount = (int) cmd.ExecuteScalar();
MessageBox.Show(“
” + friendCount.ToString() + “個聯系人”);

說明:命令可以返回多個結果,此時ExecuteScalar方法將返回第一行第一個字段的值,同時其他所有值不可訪問,這意味著如果象獲得最好的性能,您應該構造適當的select查詢,以便查詢的結果集中盡可能少的包含額外的數據。如果只對單個返回值感興趣,這個方法是首選的方法。另外該方法返回object類型數據,所有保證進行適當的類型轉換是您的責任,否則您將得到異常。
ExecuteXmlReader
方法執行返回一個xml字符串的命令。它將返回一個包含所返回的smlSystem.Xml.XmlReader對象。這個方法我一無所知,不作討論^_^

 

DataReader
1
創建DataReader對象
前面提到過沒有構造函數創建DataReader對象。通常我們使用Command類的ExecuteRader方法來創建DataReader對象:

 

SqlCommand cmd = new SqlCommand(commandText,ConnectionObject)
SqlDataReader dr = cmd.ExecuteReader();

DataReader類最常見的用法就是檢索Sql查詢或者存儲過程返回的記錄。它是連接的只向前和只讀的結果集,也就是使用它時,數據庫連接必須保持打開狀態,另外只能從前往后遍歷信息,不能中途停下修改數據。
注意:DataReader使用底層的連接,連接是它專有的,這意味這DataReader打開時不能使用對應連接進行去他操作,比如執行另外的命令等。使用完DataReader后一定記得關閉閱讀器和連接。
2
使用命令行為指定DataReader的特征
前面我們使用cmd.ExecuteReader()實例化DataReader對象,其實這個方法有重載版本,接受命令行參數,這些參數應該時Commandbehavior枚舉:
SqlDataRader dr
cmd.ExecuteReader(CommandBehavior.CloseConnection);
上面我們使用的是CommandBehavior.CloseConnection作用是關閉DataReader的時候自動關閉對應的ConnectionObject。這樣可以避免我們忘記關閉DataReader對象以后關閉Connection對象。別告訴我你不喜歡這個參數,你能保證你記得關閉連接。萬一你忘記了呢?又或者你使用你的partner開發的組件來進行開發呢?這個組件并不一定讓你有關閉連接的權限哦。另外CommandBehavior.SingleRow可以使結果集返回單個行,CommandBehavior.SingleResult返回結果為多個結果集的第一個結果集。當然Commandbehavior枚舉還有其他值,請參見msdn
3
遍歷DataReader中的記錄
ExecuteReader方法分會DataReader對象時,當前光標的位置時第一條記錄的前面。必須調用數據閱讀器的Read方法把光標移動到第一條記錄,然后第一條記錄就是當前記錄。如果閱讀器包含的記錄不止一條,Read方法返回一個booltrue。也就是說Read方法的作用是在允許范圍內移動光標位置到下一記錄,有點類似rs.movenext,不是嗎?如果當前光標指示著最后一條記錄,此時調用Read方法得到false。我們經常這樣做:
While(dr.Reader())
{
//do something with the current record
}
注意,如果你對每一條記錄的操作可能花費比較長的時間,那么意味著閱讀器將長時間打開,那么數據庫連接也將維持長時間的打開狀態。此時使用非連接的DataSet或許更好一些。
4
訪問字段的值
2種方法。第一種是Item屬性,此屬性返回字段索引或者字段名字對應的字段的值。第二種是Get方法,此方法返回有字段索引指定的字段的值。有點難以理解,不是嗎?不要緊,看例子就OK了。
Item
屬性
每個DataReader類都定義一個Item屬性。比如現在我們有一個DataReader實例dr,對應的sql語句是select Fid,Fname from friend,則我們可以使用下面的方法取得返回的值:

object ID = dr[“Fid”];
object Name = dr[“Fname”];
或者:
object ID = dr[0];
object Name = dr[0];

注意索引總是從0開始的。另外也許您發現了我們使用的是object來定義對IDName,是的,Item屬性返回的值是object型,但是您可以強制類型轉換。

int ID = (int)dr[“Fid”];
string Name = (string)dr[“Fname”];


記住:確保類型轉換的有效性是您自己的責任,否則您將得到異常。

Get
方法
起始我們在第一篇文章里面已經使用過改方法了。每個DataReader都定義了一組Get方法,比如GetInt32方法把返回的字段值作為.net clr 32位證書。同上面的例子一樣我們用如下方式訪問FidFname的值:

int ID = dr.GetInt32(0);
string Name = dr.GetString(1);

注意雖然這些方法把數據從數據源類型轉化為.net數據類型,但是他們不執行其他的數據轉換,比如他們不會把16位整數轉換為32位的。所以您必須使用正確的Get方法。另外Get方法不能使用字段名來訪問字段,也就是說上面的沒有:

int ID = dr.GetInt32(“Fid”); //
錯誤
string Name = dr.GetString(“Fname”); //
錯誤

顯然上面這個缺點在某些場合是致命的,當你的字段很多的時候,或者說你過了一段時間以后再來看你這些代碼,你會覺得很難以理解!當然我們可以使用其他方法來盡量解決這個問題。一個可行的辦法是使用const
const int FidIndex
0
const int NameIndex
1
int ID = dr.GetInt32(FidIndex);
string Name = dr.GetString(NameIndex);

這個辦法并不怎么好,另外一個好一些的辦法:
int NameIndex = dr.GetOrdinal(“Fname”); //
取得Fname對應的索引值
string Name = dr.GetString(NameIndex);

這樣似乎有點麻煩,但是當須要遍歷閱讀器種大量的結果集的時候,這個方法很有效,因為索引只需執行一次。

int FidIndex
dr.GetOrdinal(“Fid”);
int NameIndex = dr.GetOrdinal(“Fname”);
while(dr.Read())
{
int ID = dr.GetInt32(FidIndex);
string Name = dr.GetInt32(NameIndex);
}
到目前為止,我們已經討論了DataReader的基本操作了