ado.net詳細研究 —— DataReader
這次我們詳細研究DataReader。我個人最喜歡的就是DataReader,雖然它不如DataSet強大,但是在很多情況下我們須要的是靈活的讀取數(shù)據(jù)而不是大量的在內(nèi)存里面緩存數(shù)據(jù)。比如在網(wǎng)絡(luò)上每個用戶都緩存大量的dataset,這很可能導致服務(wù)器內(nèi)存不足。另外dataReader尤其適合讀取大量的數(shù)據(jù),因為它不在內(nèi)存中緩存數(shù)據(jù)。
由于下面的討論都設(shè)計到數(shù)據(jù)庫操作,我們虛擬一個小項目:個人通訊錄(單用戶),這意味著我們須要一個contract的數(shù)據(jù)庫,包含admin和fridend:
admin :Aname,Apassword
friend :Fname,Fphone,Faddress,Fid(主鍵)
當然,你可以根據(jù)自己的須要設(shè)計friend表,比如添加Fsex等字段,這里不詳細列舉。對應(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
以下的所有討論都針對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對象去連接另外的數(shù)據(jù)庫的時候,第二種方法非常有效(不過這種機會很少,一般來說一個小型系統(tǒng)只對應(yīng)一個數(shù)據(jù)庫——個人認為^_^)。
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)閉一個連接以后才能使用另外的連接。
如果你不清楚Connection對象的狀態(tài),可以使用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
連接上數(shù)據(jù)庫以后一定記得關(guān)閉連接,在ado.net中當Connection對象超出范圍時連接不會自動關(guān)閉。
打開數(shù)據(jù)庫連接以后我們要執(zhí)行命令,所以我們討論一下Command類
2 SqlCommand類
建立數(shù)據(jù)庫連接以后我們須要訪問和操作數(shù)據(jù)庫——CRUD:Create、Read、Update、Delete。
為了執(zhí)行命令我們創(chuàng)建Command對象,Comand對象要求執(zhí)行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 SqlCommand(CommandText,ConnecionObject);
另外還有一個包含三個參數(shù)的構(gòu)造函數(shù),我們不討論。設(shè)計到的是事務(wù)處理。
有了Command對象以后我們須要執(zhí)行操作,但是執(zhí)行前請一定記得打開你的數(shù)據(jù)庫連接,否則會有異常。SqlCommand對象提供下面4個執(zhí)行方法:
l ExecuteNonQuery
l ExecuteScalar
l ExecuteReader
l ExecuteXmlReader
ExecuteNonQuery方法執(zhí)行不返回結(jié)果的命令,通常使用它執(zhí)行插入、刪除、更新操作。
例如我們對contract數(shù)據(jù)庫進行操作:
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方法執(zhí)行返回單個值的命令,例如我們須要統(tǒng)計系統(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() + “個聯(lián)系人”);
ExecuteXmlReader方法執(zhí)行返回一個xml字符串的命令。它將返回一個包含所返回的sml的System.Xml.XmlReader對象。這個方法我一無所知,不作討論^_^。
DataReader類
1. 創(chuàng)建DataReader對象
前面提到過沒有構(gòu)造函數(shù)創(chuàng)建DataReader對象。通常我們使用Command類的ExecuteRader方法來創(chuàng)建DataReader對象:
SqlCommand cmd = new SqlCommand(commandText,ConnectionObject)
SqlDataReader dr = cmd.ExecuteReader();
注意:DataReader使用底層的連接,連接是它專有的,這意味這DataReader打開時不能使用對應(yīng)連接進行去他操作,比如執(zhí)行另外的命令等。使用完DataReader后一定記得關(guān)閉閱讀器和連接。
2. 使用命令行為指定DataReader的特征
前面我們使用cmd.ExecuteReader()實例化DataReader對象,其實這個方法有重載版本,接受命令行參數(shù),這些參數(shù)應(yīng)該時Commandbehavior枚舉:
SqlDataRader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
上面我們使用的是CommandBehavior.CloseConnection,作用是關(guān)閉DataReader的時候自動關(guān)閉對應(yīng)的ConnectionObject。這樣可以避免我們忘記關(guān)閉DataReader對象以后關(guān)閉Connection對象。別告訴我你不喜歡這個參數(shù),你能保證你記得關(guān)閉連接。萬一你忘記了呢?又或者你使用你的partner開發(fā)的組件來進行開發(fā)呢?這個組件并不一定讓你有關(guān)閉連接的權(quán)限哦。另外CommandBehavior.SingleRow可以使結(jié)果集返回單個行,CommandBehavior.SingleResult返回結(jié)果為多個結(jié)果集的第一個結(jié)果集。當然Commandbehavior枚舉還有其他值,請參見msdn。
3. 遍歷DataReader中的記錄
當ExecuteReader方法分會DataReader對象時,當前光標的位置時第一條記錄的前面。必須調(diào)用數(shù)據(jù)閱讀器的Read方法把光標移動到第一條記錄,然后第一條記錄就是當前記錄。如果閱讀器包含的記錄不止一條,Read方法返回一個bool值true。也就是說Read方法的作用是在允許范圍內(nèi)移動光標位置到下一記錄,有點類似rs.movenext,不是嗎?如果當前光標指示著最后一條記錄,此時調(diào)用Read方法得到false。我們經(jīng)常這樣做:
While(dr.Reader())
{
//do something with the current record
}
注意,如果你對每一條記錄的操作可能花費比較長的時間,那么意味著閱讀器將長時間打開,那么數(shù)據(jù)庫連接也將維持長時間的打開狀態(tài)。此時使用非連接的DataSet或許更好一些。
4. 訪問字段的值
有2種方法。第一種是Item屬性,此屬性返回字段索引或者字段名字對應(yīng)的字段的值。第二種是Get方法,此方法返回有字段索引指定的字段的值。有點難以理解,不是嗎?不要緊,看例子就OK了。
Item屬性
每個DataReader類都定義一個Item屬性。比如現(xiàn)在我們有一個DataReader實例dr,對應(yīng)的sql語句是select Fid,Fname from friend,則我們可以使用下面的方法取得返回的值:
object ID = dr[“Fid”];
object Name = dr[“Fname”];
或者:
object ID = dr[0];
object Name = dr[0];
int ID = (int)dr[“Fid”];
string Name = (string)dr[“Fname”];
記?。捍_保類型轉(zhuǎn)換的有效性是您自己的責任,否則您將得到異常。
Get方法
起始我們在第一篇文章里面已經(jīng)使用過改方法了。每個DataReader都定義了一組Get方法,比如GetInt32方法把返回的字段值作為.net clr 32位證書。同上面的例子一樣我們用如下方式訪問Fid和Fname的值:
int ID = dr.GetInt32(0);
string Name = dr.GetString(1);
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對應(yīng)的索引值
string Name = dr.GetString(NameIndex);
這樣似乎有點麻煩,但是當須要遍歷閱讀器種大量的結(jié)果集的時候,這個方法很有效,因為索引只需執(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的基本操作了

