13. ADO物件模型

微軟ActiveX資料物件(ADO)架構比起DAO或RDO物件模型來說較不複雜的。然而,ADO架構的簡易性並不表示學習ADO是較簡單的。即使ADO物件模型有著比DAO與RDO還要少的物件與集合,但他們卻是比起DAO或RDO同質的元素還要複雜些。還有少部份ADO物件有著DAO所沒有的事件。

圖13-1表示完整的ADO 2.0物件模型。正如您所見,ADO有三個主要的獨立物件-Connection物件,Recordset物件與Command物件-每個都有兩個集合。Connection,Recordset,與Command物件並未嚴格地關連。可在程式中隱含地建立其關係-例如,藉由指定Connection物件給Recordset物件的 ActiveConnection 屬性。這個在資料庫物件間建立關係的能力讓您有許多彈性,這些是DAO與RDO所沒有的。

在這個章節,將著重討論ADO中各物件的屬性、方法和事件。( 第14章 ,將描述如何在應用程式中使用這些物件)。為了示範ADO的運作,筆者準備了ADO Workbench應用程式,可與您互動地建立Connection、Command與Recordset物件;執行其方法;察看屬性的變化;與事件的引發等(如 圖13-2 )。此程式滿複雜的,大約有10個模組與2000行程式碼,不過若要不寫程式碼而練習ADO的話,它是滿有用的。事實上,當筆者撰寫此程式時,筆者還發現許多關於ADO有趣的用途,14章會說明這些用途。


 

圖13-1 ADO 2.0的物件模型

ADO物件模型比起DAO和RDO物件模型較簡單的一個原因是它有較少的集合(Collections),而DAO和RDO則充滿許多集合。例如,在ADO中,可以建立許多Connection和Recordset物件,不過都是獨立物件,而物件等級(Object Hierarchy)並不會對它們保持一個引用關係。乍看之下,可能會認為需要追蹤所有活動中的物件,並將它們存入自訂的集合內,假若後續還需要使用它們時,會讓程式變得有點麻煩。但當深入觀察後,將瞭解關於獨立物件的處理方式會大大地簡化程式架構,因為絕大部份時候,並不必寫清除程式碼:當這個物件離開範圍時,ADO會知道它的結束,且若有需要的話,會自動地關閉開啟中的Recordset與Connection。這個方法減少了記憶體的遺失與讓應用程式不需要太多的資源。


 

圖13-2執行中的ADO Workbench應用程式

從本章其他部份中您將了解,ADO雖未彌補其較少的物件,但讓每個物件擁有較多的屬性(比起DAO與RDO模型中雷同的物件)。所有主要的ADO物件包含一個Properties集合,其包含許多給OLE DB Provider用的Dynamic Properties(動態屬性),針對Provider特殊的特性而定。無須對於動態屬性感到困惑,其能否使用端視OLE DB Provider而定,而內建屬性都是可用的,不管使用什麼Provider。

Connection物件
 

ADO Connection物件表示一個通往資料來源的開啟連線(Connection)。此資料來源可能是個資料庫,ODBC來源,或任何其他OLE DB提供者存在的來源。Connection物件可指定所有需要的參數-例如,Server與資料庫名稱、使用者名稱和密碼、與中斷時間-在開啟資料來源前。Connection物件也是重要的,因為它們可進行交易行為。每個Connection屬於一個指定的客戶端應用程式,且只有明白地關閉它時才會關閉,亦即設定物件變數為Nothing或當應用程式結束時。

屬性
 

Connection物件並未含有許多特性。底下筆者把這些屬性依照其目的分門別類,如此比起條列式地列出來要好多了:預備連線,交易行為的管理,決定連線狀態與哪個版本的ADO在執行中。

預備連線
 

許多特性可用來指定要開啟哪個資料庫與用何種模式開啟。在開啟連線前,這些屬性都能被寫入,但在連線建立後就變成唯讀了。Provider 屬性是作為連線用OLE DB provider的名稱-例如,「SQLOLEDB」是微軟SQL伺服器OLE DB的提供者(Provider)。假若未指定此屬性,會用預設值MSDASQL Provider,其為ODBC Driver的OLE DB Provider,它是一種允許連線到大部分關連式資料庫的橋樑,即使其OLE DB Provider尚未開發。MSDASQL也稱為Kagera。除了指定 Provider 屬性值外,也可將提供者名稱置於 ConnectionString(連線字串)內與其他OLE DB Provider的特性一起傳遞。例如下例,連線字串開啟Biblio.mdb資料庫:

Dim cn As New ADODB.Connection
cn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.3.51;" _
    & "Data Source=C:\Microsoft Visual Studio\Vb98\Biblio.mdb"

下列的連線字串為開啟在伺服器名稱(Server name)為ServerNT機器上的SQL Server資料庫軟體中的Pubs資料庫。(此連線字串也包含使用者名稱與密碼)。

cn.ConnectionString = "Provider=SQLOLEDB;Server=ServerNT;" _
    & "User ID=MyID;Password=MyPWD;Data Source=Pubs"

你不可同時在 ConnectionString 和 Provider 屬性中指定提供者名稱,因為此結果是不可預期的。

一個簡單建立連結字串的方法是放置一個ADO資料控制項(Data Control)於表單上,開啟其屬性頁,選擇使用連線字串選項,並按下建立按鈕。會開啟新的對話方塊,於此可以選擇提供者,使用者名稱、密碼與所有與提供者相依的動態屬性,如 圖13-3 所示。當完成後,完成的連線字串會出現在屬性頁的一般頁籤。

要熟知 ConnectionString 屬性的語法有點困難,因為此字串包含許多不同的參數(其格式為「參數=值」)。當連到ODBC來源時,此工作事實上又更複雜了。ConnectionString 屬性也支援可與新OLE DB特性共存的ODBC特性。表13-1列出可用在此字串內最常用的特性。

表13-1一些可用在ConnectionString中的參數
參數 說明
Data Source 所要連結的SQL Server或MDB資料的名稱。當連到ODBC來源時,此參數也可是DSN名稱。
DSN 註冊在現行機器上的ODBC來源名稱,此參數可被Data Source參數取代。
Filename 包含關於連線資訊的檔案;此參數可以是ODBC DSN檔案或Microsoft Data Link(UDL)檔案。
Initial Catalog 預設資料庫名稱。當連到ODBC來源時,也可使用Database參數。
Password 使用者密碼。當連到ODBC來源時,可使用PWD參數。假如連到SQL Server且使用信任機制的話,並不需要傳遞使用者ID與密碼。
Persist Security Info 若ADO將使用者ID與密碼存在Data Link中的話,此值為True。
Provider OLE DB Provider名稱;預設值為MSDASQL,ODBC來源的提供者。
User ID 使用者名稱。當連到ODBC來源時,可使用UID參數。

DefaultDatabase 屬性是連線預設資料庫的名稱。許多ADO對話方塊以此來初始Catalog。此屬性直到連線開啟後才能使用,且是唯讀(Read-Only)的。

Connection物件包含兩個屬性用來調整應用程式的網路與資料庫速度。ConnectionTimeout 屬性決定嘗試建立連線時引發逾時錯誤的ADO等待的秒數。(預設為15秒)。CommandTimeout 屬性為ADO等候資料庫命令或查詢完成的秒數。(預設為30秒)。此值只用在執行於此Connection物件的查詢而已;不包含使用相同連線的Command物件。(Command物件只備齊自有的 CommandTimeout 屬性所影響)。


 

圖13-3 可以使用ADO資料控制項的屬性頁來建立連線字串。「全部」頁籤顯示所選取的OLE DB Provider的所有屬性(此例為SQLOLEDB)。

CursorLocation 決定是否要建立指標,若要的話,要建立在哪?可能的值有2-adUserServer(Server端或驅動程式提供的指標)與3-adUserClient(Client端指標,包括離線Recordset)。(離線Recordset是不與活動中的Connection物件有關連的Recordset)。Mode 屬性決定連線的全線。其值列表如下:

說明
1-adModeRead 允許read-only資料存取
2-adModeWrite 允許write-only資料存取
3-adModeReadWrite 允許read與write資料存取
4-adModeShareDenyRead 防止其他Client對開啟中的連線有read權限
8-adModeShareDenyWrite 防止其他Client對開啟中的連線有write權限
12-adModeShareExclusive 防止其他Client存取連到已開啟使用的相同資料來源
16-adModeShareDenyNone 允許其他Client開啟有所有權限的連線

若此屬性沒有指定值的話,預設為 0-adModeUnknow。只有當連線關閉時才能將值設給此屬性。對於開啟中的連線而言,此屬性是唯讀的。

交易行為的管理
 

IsolationLevel (隔離等級)屬性影響連結中交易該如何處理。其為bit欄位屬性,可以是下列值的和:

說明
&H10-adXactChaos 不能從較高的獨立交易中覆蓋尚未決定的改變
&H100-adXactBrowse 在其他交易完成前,可檢視其改變
&H1000-adXactCursorStability 不能檢視其他交易的改變,直到交易完成
&H10000-adXactRepeatableRead 無法看到其他交易的改變,但若重複查詢則可看見
&H100000-adXactIsolated 兩兩交易間是相互獨立的
-1-adXactUnspecified 獨立的等級無法確定

IsolationLevel 是可讀寫的,但其值的改變需等下個 BeginTrans 執行時才會發生作用。Provider沒必要支援列於上表的所有isolation等級:若要求一個未支援的isolation等級時,此Provider會強迫到下一較大的等級。

Attributes 屬性包含兩個bit,其影響當交易完成或返回時會發生什麼事情。Bit(&H20000-adXactCommitRetaining)在每個 CommitTrans 方法後會自動開始新的交易。而bit(&H40000-adXactAbortRetaining)則會在每個 RollbackTrans 方法後開始新的交易。然而,並非所有的Provider允許您在每個 CommitTrans 與 RollbackTrans 方法後自動開始新的交易。

測試狀態與版本
 

State 屬性是唯讀的,且為bit欄位的屬性,其傳回Connection的現在狀態。可以是下列值的和:

說明
0-adStateClosed Connection已關閉
1-adStateOpen Connection已開啟
2-adStateConnecting Connection即將開啟,連線中。
4-adStateExecuting Connection正執行命令
8-adStateFetching 即將傳回Recordset

當不確定Connection物件的狀態時,應要查詢此屬性,因為當物件關閉或讀取資料時,許多屬性是不可用的。Connection物件最後一個屬性,Version,傳回一個唯讀屬性,表示使用中的ADO版本。例如在ADO 2.0下,此屬性傳回「2.0」。

方法
 

Connection物件的方法可作四件事:開啟連線、執行命令、管理活動中連線的交易行為與決定資料表的結構。

建立連線
 

第一個參數與 ConnectionString 屬性有著相同的意義,UserID為使用者登入名稱,而 Password 為使用者密碼。若 Options 被設為16-adAsyncConnect,此連線以非同步模式來開啟,且直到 ConnectComplete 事件被引發後,此連線才能使用。所有的參數皆為可選擇的,但若有傳遞 UserID 與 Password 參數的話,就不該在 ConnectionString 參數或屬性中指定他們。例如,下列敘述開啟非同步連線,連到SQL Server的Pubs資料庫,且指定「sa」為登入代號,而沒有密碼:

Dim cn As New ADODB.Connection
cn.Open "Provider=SQLOLEDB;Data Source=MyServer;Initial Catalog=Pubs;" _
    & "User ID=sa;Password=;", , , adAsyncConnect

要關閉連線,請使用 Close 方法,其沒有任何參數。

執行資料庫命令與查詢
 

Execute 方法於連線上執行一個動作查詢或SELECT查詢。此方法的語法取決於動作的形態:若執行不傳回Recordset的動作(例如INSERT、UPDATE、DELETE等SQL敘述),下列為其正確的語法:

Execute CommandText, [RecordsAffected], [Options]

CommandText 為預存程序、資料表、或任何SQL查詢的名稱。RecordsAffected 為Long形態的變數,表示被此命令所影響的記錄筆數。Options 表示 CommandText 屬性的內容要如何為何種形態,其值可以是:

說明
1-adCmdText SQL查詢文字
2-adCmdTable 資料表
4-adCmdStoredProc 預存函式
8-adCmdUnknown 未定義;由Provider決定
512-adCmdTableDirect 直接開啟的資料表(應避免使用在SQL Server資料庫上)

若傳值為adCmdUnknown或省略 Options 參數,OLE DB Provider通常是可找出運作的形態為何,但會增加些負荷。基於此,最好總是給個正確值。若執行傳回Recordset的命令,Execute 方法的語法有點不同:

Execute(CommandText, [RecordsAffected], [Options]) As Recordset

要將此方法的結果指定給一個Recordset物件,如此往後才能瀏覽此結果。Execute 命令只能建立有著預設設定的Recordset物件-亦即,唯讀、往前、且快取大小(cache size)為1的Recordset。

在 Options 參數中增加16-adAsyncExecute可執行非同步命令。若決定要非同步地發佈Recordset,則在Option參數中增加32-adAsyncFetch。不論是否有指定非同步選項,當 Execute 命令完成時,總是會引發 ExecuteComplete 事件。

可在任何時候執行 Cancel 方法來取消非同步運作。此方法沒有任何參數。沒有必要指出哪個運作要被刪除,因為在一個給定的連結上,同時只能有一個非同步運作在進行。

開始與結束交易
 

BeginTrans、CommitTrans 與 RollbackTrans 方法控制交易的開始與結束。執行 BeginTrans 方法可開始一個交易:

level = cn.BeginTrans

此方法傳回交易等級:1為最高層交易,而未被包含在其他交易內,2為包含在最高層交易的交易...等等。若Provider未支援交易行為,則 BeginTrans、CommitTrans 與 RollbackTrans 方法會傳回錯誤。可藉由檢查是否Connection物件包含 Transaction DDL 屬性來得知是否Provider支援交易行為:

On Error Resume Next
value = cn.Properties("Transaction DDL") 
If Err = 0 Then
    level = cn.BeginTrans
    If level = 1 Then
        MsgBox "A top-level transaction has been initiated"
    Else
        MsgBox "A nested transaction has been initiated"
    End If
Else
    MsgBox "This provider doesn't support transactions"
End If

CommitTrans 方法完成現行的交易-亦即其確定所有資料庫的改變。相反地,RollbackTrans 方法還原現行的交易,如此會還原交易開始後的所有改變。只有當交易為最高層交易時方能確定 CommitTrans 方法會確認資料的寫入:另一方面,現行交易會包在其他可還原的交易內。

Attributes 屬性值決定當確認或還原交易時會怎樣。若Attributes 屬性有個adXactCommitRetaining位元設定,則在 CommitTrans 方法後,Provider會自動開始另一個新交易;若 Attributes 屬性有個adXactAbortRetaining位元設定的話,則在每個 RollbackTrans 方法後,Provider會開始另一個新交易。

決定資料表的結構
 

最後一個方法是 OptnSchema。此方法查詢資料來源,且傳回包含結構資料(資料表明稱、欄位名等)的Recordset。不過筆者並不希望您常用此方法,因為ADO 2.1擴充了ADO物件模型,可讓您得到關於資料來源結構的資料,只要藉由某種物件導向方法即可,本章最後會加以說明。若使用此方法,要注意其中有臭蟲(Bug):它不支援Server端Recordset,不幸的是其為ADO的預設值。因此,若使用 OpenSchema 方法,記得在開啟Recordset前,要設定Connection的 CursorLocation 屬性為adUseClient。

事件
 

Connection物件包含九個事件。並非所有事件都有相同的語法,但有些樣式會重複出現,說明這些樣式要比起解釋每個事件來的重要些。

大部分的ADO事件是成對出現的。例如Connection物件包含 WillConnection與 ConnectComplete 事件,其在連線建立的前後被引發。另一個例子,WillExecute 與 ExecuteComplete 於命令執行前後引發。這些 Willxxxx 與 xxxxComplete 事件的關鍵是adStatus參數。

對於 Willxxxx 事件而言,此參數可為1-adStatusOK(沒錯誤)、2-adStatusErrorsOccurred(錯誤產生)、3-adStatusCantDeny(沒錯誤M且此動作不能被取消)。若要取消此動作,可於事件處理函數將 adStatus 參數值修改為4-adStatusCancel,或若不要再從ADO物件收到此事件,可將之改成5-adStatusUnwantedEvent。若事件函數收到 adStatus 值為adStatusCantDeny,則不能將之改成adStatusCancel值。

相同的狀態值也可用在 xxxxComplete 事件,不過由於動作皆已完成,所以不能將之設成adStatusCancel。即使再 Willxxxx 事件內取消此動作,相對的 xxxxComplete 仍會引發,但其傳入的 adStatus 值為adStatusCancel。當取消此動作時,程式收到錯誤3712-動作已被使用者取消-即使在 xxxxComplete 事件函數中重設Errors集合或 adStatus 參數亦同。

許多ADO事件的最後一個參數指向引發此事件的物件,此參數在Visual Basic內並不需要:因為只能捕抓來自獨立物件的事件,所以已經有此物件本身的引用了。其他程式語言,例如Microsoft Visual C++-可撰寫捕抓引發自多重物件的事件的事件函數,這時候物件引用便是必需的,以便得知事件引發自何處。

連線類事件
 

快速看一下Connection物件的事件。當連線企圖執行 Open 方法時會引發WillConnect 事件。四個參數會傳給 Open 方法,再加上 adStatus 參數與指向Connection物件本身的參數:

Private Sub cn_WillConnect(ConnectionString As String, UserID As String, _
    Password As String, Options As Long, _
    adStatus As ADODB.EventStatusEnum, _
    ByVal pConnection As ADODB.Connection)

可用此方法來修改連線字串,使用者代號或密碼。當連線運作完成時-不論是否成功-Connection物件皆會引發 ConnectComplete 事件,其接收一個Error物件與 adStatus 參數:

Private Sub cn_ConnectComplete(ByVal pError As ADODB.error, _
    adStatus As ADODB.EventStatusEnum, _
    ByVal pConnection As ADODB.Connection)

Connection物件也包含 Disconnect 事件,當連線關閉時會引發:

Private Sub cn_Disconnect(adStatus As ADODB.EventStatusEnum, _
    pConnection As Connection)

於 ConnectComplete 與 Disconnect 事件內設定 adStatus 為adStatusUnwantedEvent並沒有用。

執行類事件
 

在連線上的任何命令要執行前會引發 WillExecute 事件:

Private Sub cn_WillExecute(Source As String, _
    CursorType As ADODB.CursorTypeEnum, LockType As ADODB.LockTypeEnum, _
    Options As Long, adStatus As ADODB.EventStatusEnum, _
    ByVal pCommand As ADODB.Command, _
    ByVal pRecordset As ADODB.Recordset, _
    ByVal pConnection As ADODB.Connection)

Source為SQL字串或預存函式名稱。CursorType 表示指標的形態。(關於Recordset物件的 CursorType 屬性,請參閱本章後面的 〈指標運作〉 一節)。LockType 為所傳回Recordset之鎖定形態。Options 用法跟 Execute 方法的同名參數一樣。若命令不傳回Recordset,則 CursorType 與 LockType 參數被設為-1。因為所有參數皆為傳址呼叫,若需要的話可修改他們。最後三個參數指向事件來源的Connection、Command、Recordset物件。 Pconnection 參數總是指向活動中的Connection物件,當Connection的 Execute 方法、Command的 Execute 方法或Recordset 的 Ope 方法要執行時,皆會引發此事件。

當預存函式的執行或SQL查詢結束時會引發 ExecuteComplete 事件:

Private Sub cn_ExecuteComplete(ByVal RecordsAffected As Long, _
    ByVal pError As ADODB.error, adStatus As ADODB.EventStatusEnum, _
    ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, _
    ByVal pConnection As ADODB.Connection)

RecordsAffected 為動作所影響的記錄筆數(此值亦會於Execute方法的第二個參數所傳回)。Perror 與 adStatus 意義跟以往相同。最後三個參數指向引發事件的物件。

交易類事件
 

當 BeginTrans 方法已完成其執行時會引發 BeginTransComplete 事件。第一個參數為將傳回給程式的值-亦即為剛初始交易的等級。所有其他參數的意義應是很明顯的。

Private Sub cn_BeginTransComplete(ByVal TransactionLevel As Long, _
    ByVal pError As ADODB.error, adStatus As ADODB.EventStatusEnum, _
    ByVal pConnection As ADODB.Connection)

CommitTransComplete 與 RollbackTransComplete 事件的語法與BeginTransComplete 相似,只是沒有關於交易等級的資訊傳給此事件罷了。

Private Sub cn_CommitTransComplete(ByVal pError As ADODB.error, adStatus _
    As ADODB.EventStatusEnum, ByVal pConnection As ADODB.Connection)

Private Sub cn_RollbackTransComplete(ByVal pError As ADODB.error, adStatus 
    As ADODB.EventStatusEnum, ByVal pConnection As ADODB.Connection)

其他事件
 

Connection物件所包含其他的事件為 InfoMessage。當資料庫引擎傳送訊息、警告或當預存程序執行PRINT或RAISERROR SQL敘述時,會引發此事件。

Private Sub cn_InfoMessage(ByVal pError As ADODB.error, adStatus As _
    ADODB.EventStatusEnum, ByVal pConnection As ADODB.Connection)

絕大部份,要測試 pError 參數或檢查Errors集合內的元素以便了解到底發生了什麼事。

Errors集合
 

Connection物件有個 Errors 屬性,其傳回發生於此連線上之所有錯誤的集合。更正確的說法是,每次當有錯誤發生時,Errors集合會被清除,然後會填入在程式與資料來源間所發生的錯誤,包含ODBC Driver(若您使用MSDASQL OLD DB Provider)與資料庫引擎本身。可檢查集合內的所有項目以便找出錯誤來自何處,與其如何解釋這些錯誤。於此集合中並不會發現ADO錯誤-例如,當傳遞不合法值給ADO屬性或方法時所引發的錯誤-因為那些錯誤被當成是一般性的Visual Basic錯誤,該用標準的錯誤偵查處理來管理之。

在集合內的每個Error物件包含一些屬性,可讓您對錯誤有近一步的了解。Number、Description、HelpFile 與 HelpContext 屬性的意義與Visual Basic Error物件中的相同屬性一樣。若要追蹤錯誤發生的地方,Source 屬性特別重要。SQLState 與 NativeError 屬性傳回SQL資料來源錯誤的相關資訊。ODBC來源所傳回的錯誤定義在ODBC 3規格中。

當執行 Clear 方法時,ADO會清除Errors集合。然而,只有當錯誤真正發生時,ADO清除Connection物件的Errors集合。基於此,在執行可能會導致錯誤的Connection物件的方法前,手動清除此集合是方便的。

Recordset物件
 

Recordset物件包含所有從資料庫讀出或寫入的資料。一個Recordset可以包括數列或數行的資料。每一列是一筆記錄;記錄中的欄位為行。同一時間只能存取一列,所以該列稱為目前資料列或目前記錄。藉由改變目前的記錄可瀏覽一個Recordset。

ADO Recordset物件比起DAO和RDO中同質的物件有更多的變化。例如,可以自行建立一個ADO Recordset物件而不須連結資料庫。或者可從資料庫擷取一個Recordset,關閉其連結,修改在Recordset內的資料,最後重新連結並將所有更新的資料傳送給Server。(RDO也有樂觀批次更新,但DAO沒有。) 甚至可以將ADO Recordset儲存成檔案,以便稍後可再復原。

在附贈的CD中,ADO Workbench應用程式可讓您了解Recordset物件的許多屬性。也可以執行其方法並看看哪些事件會引發。應用程式解譯所有ADODB函式庫內符號常數的意義,如圖13-4。


 

圖13-4 可用ADO Workbench應用程式去察看Recordset的屬性,執行Recordset方法並看Recordset事件的引發;另一個視窗可瀏覽Fields集合與記錄的實際內容。

屬性
 

Recordset物件為ADO物件模型中具有最多屬性的物件。隨後將這些屬性依其功能而不是名稱來分類。

設定Recordset來源
 

Recordset物件最重要的屬性可能為 Source 屬性,此屬性包括表格的名稱、儲存程序的名稱或SQL查詢文字。Source 屬性被宣告為Variant,表示允許指定一個Command物件給它。如果指定一個Command物件給 Source 屬性,其會傳回Command物件的 CommandText 屬性內容,而不是對應到Command物件。對於關閉的Recordset物件而言,Source 屬性是可讀寫的;而在Recordset被開啟後則成為唯讀的。以下為 Source 屬性的例子:

' Edit this constant to match your directory structure.
Const DBPATH = "C:\Program Files\Microsoft Visual Studio\Vb98\NWind.mdb"
Dim cn As New ADODB.Connection, rs As New ADODB.Recordset
cn.Open "Provider=Microsoft.Jet.OLEDB.3.51;Data Source=" & DBPATH
rs.Source = "Employees"
rs.Open , cn

若傳遞此屬性值給 Open 方法的第一個參數,可使程式碼更為簡明:

rs.Open "Employees", cn

當指定一個ADO Command物件給 Source 屬性後,稍後可透過ActiveCommand 屬性去取得此物件的參考。

為了開啟Recordset,必須將之關連到一個已存在的Connection。可明確地建立Connection物件並將之指定給 ActiveConnection 屬性,或者可藉著指定一個連結字串給 ActiveConnection 屬性來隱含地建立它:

' Edit this constant to match your directory structure.
Const DBPATH = "C:Program Files\Microsoft Visual Studio\VB98\NWind.mdb"

' First method: explicit Connection object
cn.Open "Provider=Microsoft.Jet.OLEDB.3.51;Data Source=" & DBPATH_
Set rs.ActiveConnection = cn
rs.Source = "Employees"
rs.Open

' Second method: implicit Connection object
rs.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.3.51;" _
    & "Data Source= " & DBPATH
rs.Source = "Employees"
rs.Open

當建立一個隱含的Connection物件時,稍後可透過 ActiveCommand 屬性來引用之(例如,要存取Connection的錯誤集合時)。在記錄被開啟後,或當Command物件已被指定給 ActiveCommand 屬性後,ActiveConnection 屬性就成為唯讀了。

還有另一個方法可讓Recordset取得資料,就是透過 DataSource 和DataMember 屬性。例如,可藉著下列敘述來連結一個Recordset物件到一個ADO Data控制項。

Set rs.DataSource = Adodc1

並不需要設定其他屬性,也不必呼叫 Open 方法(事實上這樣會發生錯誤)。如果資料來源是個DataEnvironment物件,還必須指定有效的字串給DataMember屬性;否則,當設定 DataSource 屬性時,會導致錯誤。

Recordset的 State 屬性傳回Recordset現行的狀態,其值為一組可單獨測試之獨立位元:

說明
0-adStateClosed Recordset是關閉狀態
1-adStateOpen Recordset為開啟狀態
2-adStateConnecting Recordset正連線中
4-adStateExecuting Recordset正在執行某命令
8-adStateFetching Recordset的資料列正被擷取中

最後三個值只出現在當Recordset物件正執行非同步方法時。

指標(Cursors)的運作
 

指標是一組表示查詢結果的記錄。指標可包含實際的資料或只是指向資料庫記錄的指標,但是取得資料的機制對程式設計師而言是很清楚的。可指定指標要被建立在哪(在用戶端或伺服器工作站)、類型和鎖定度。

CursorLocation 屬性指定指標要建立在哪裡。此屬性可有兩個值可做選擇:2-adUseServer或3-adUseClient。此屬性值繼承自Connection物件且對於關閉的Recordsets而言,此屬性可被改變。當使用ODBC驅動程式和SQL Server的OLE DB提供者時,預設的指標是在伺服器的forward-only(指標位置祇能往前)指標(這類型的指標是非常有效率的)。如果要建立分離的Recordsets和使用樂觀的批次更新策略,需要轉換為Client-Side指標。當有個連結到Recordset的DataGrid控制項或者其他複雜的控制項時,Client-Side指標通常較是好的選擇。在其他情況下,Server-Side指標則通常是較好的,因為他們提供較好的性能和額外的指標類型。

CursorType 屬性說明要建立哪類的指標,有下列常數可做選擇:0-adOpenForwardOnly、1-adOpenKeyset、2-adOpenDynamic、或3- adOpenStatic。Server-Side指標支援所有的選項,然而Client-Side指標只支援3-adOpenStatic。但是如果對Client-Side指標使用不同設定,則會自動建立Static指標而不會產生錯誤。

Forward-only類的指標預設為Server-Side指標,且只能設為Server-Side指標類型。如之前所述,此類型的指標是非常有效率的,尤其若設定LockType等於adReadOnly及CacheSize = 1時。許多程式設計師稱呼這類型的指標為「無指標」。William R. Vaughn所著的《The Hitchhiker's Guide to Visual Basic and SQL Server》書中定義此為「Fire-Hose」指標,在於強調其將資料拋給用戶端應用程式時相當快的。要使用此類指標,並不需要做什麼事,因為它是ADO的預設指標。對於forward-only Recordset,只能藉由 MoveNext 方法來瀏覽其資料。若對於需要更新資料的程式而言,想要有最佳的效能,應要透過SQL指令或預存程序進行所有更新資料的動作。

Dynamic指標是由一群在資料來源中實際資料的書籤(Bookmark)所組成。任何時候從客戶端要求記錄時,ADO會使用書籤去讀取目前值,意指應用程式總是讀取由其他使用者儲存的最近值。當其他使用者已經新增或刪除一筆記錄或改變在Recordset中的任何記錄時,Dynamic指標會自動地更新資料。不意外地,此類指標在性能上和網路傳輸上是最耗時的,因為任何時候,移動另一個記錄到伺服器的過程時,皆需要取得目前值。對於動態Recordset,總是可更新資料和執行所有種類的瀏覽方法,包含使用書籤(如果提供者支援的話)。此類型的指標只能為Server-Side的指標。


說明

Microsoft Jet Engine不支援Dynamic游標,所以如果嘗試用Jet OLE DB Provider來開啟動態游標會得到keyset指標。


Keyset 指標類似Dynamic指標,但是其不包含由其他使用者增加的記錄。可以讀取和修改指標內的所有記錄,但若存取其他使用者刪除的記錄會得到錯誤。Keyset指標只能為Server-Side指標。

Static指標建立被 Source 屬性定義的所有記錄的完全可捲動快取,且其是唯一可為Client-Side指標的類型。因為Static指標實際上是來自資料庫資料的複製,其他使用者所做的改變是不可見的。然而此指標比起Forward-Only指標而言是較沒效率的,且會增加指標所在電腦的負荷量,但特別對於未包含太多記錄的Recordset,這類指標是最佳的選擇。對於自預存程序取得資料而言,Static指標通常是最好的選擇。依據Provider和其他設定,此Recordset甚至是可被更新的。當客戶端工作站有足夠的記憶體時,指應該建立Client-Side Static指標。

當使用指標來作業時,MaxRecords 屬性設定Recordset傳回記錄筆數的上限。MaxRecords 屬性預設值是0,代表所有的記錄將被傳回。當Recordset已關閉時,此屬性可被寫入,而當Recordset是開啟狀態時,則為唯讀。

當使用指標作業時,CacheSize 屬性設定並傳回ADO在區域端快取的記錄筆數。可調整此屬性值使得應用程式執行更順暢,提升記憶體效能。可在任何時刻設定新值給此屬性,但若Recordset已開啟,只在ADO需要去填區域端的快取時,新設定的值才會被使用-亦即,當移動在目前記錄的指標到未在快取區的記錄時。


說明

大部分的程式設計師喜歡指標(Cursor)-特別是Dynamic和keyset指標,因為它們是如此的有力和多用途的。可惜,從表現、資源和延展性來看,指標通常是最壞的選擇。當使用筆數小的Recordsets作業,或者當使用Bound控制項(Bound控制項需要指標來支援向後性和向前性),則應該使用指標。當利用指標時,記得去建立 Source 屬性以便減少資料列的擷取,和盡量使用索引作為Where語法的條件。另一個有效率的技巧是執行 MoveLast 方法以便快速發佈Recordset和釋放任何鎖住的資料來源。


平行處理
 

所有多使用者的資料庫都會有鎖定策略。為了避免多使用者在相同時刻針對相同記錄做改變(如此將可能會產生不一致的資料庫),必須使用鎖定。以延展性來看,鎖定是很耗費資源的;當鎖定正被使用者修改的記錄時,則沒有其他使用者可以存取相同之紀錄。根據應用程式的情況,鎖定代表著降低效能,且若沒採取良好的鎖定解決策略的話,其可能導致嚴重的錯誤。

LockType 屬性表示資料庫的資料鎖定為何種類型。此屬性可為下列值之一:1-adLockReadOnly、2-adLockPessimistic、3-adLockOptimistic和4-adLockBatchOptimistic。

此屬性的預設值是adLockReadOnly,其建立不能更新的Recordset。這是最有效率的選項,因為其表示對於資料而言,並沒有寫入鎖定。對於所關心的延展性而言,其也是最好的選擇。當讀取資料時,一個好的方式是採用Forward-Only與ReadOnly的無指標模式(ADO預設值),此須透過SQL語法或預存函數來進行所有的更新動作。

當使用悲觀鎖定時,一旦進入編輯模式(即修改Recordset上的某個欄位時),ADO會嘗試鎖定該記錄。只有在 Update 方法或移到另一筆記錄後,它才會解除鎖定。當一筆記錄被鎖定時,沒有其他使用者可以進行寫入動作。基於此,當應用程式的使用者介面會讓使用者在Recordset中自由地瀏覽時,應該決不使用悲觀鎖定。(除非當任何其中之一休息時,想要暫停所有使用者的動作)。悲觀鎖定只對Server-Side指標有效。

樂觀鎖定比悲觀鎖定來得好些,但若使用此類鎖定,則程式設計師需要多加留意。採用樂觀鎖定,當記錄被更新時(通常只花很少的時間),ADO會鎖定目前記錄。

樂觀批次鎖定是個特別模式,它只對Client-Side Static指標有效。在樂觀批次鎖定中,客戶端的機器會下載所有資料,讓使用者執行所有需要的改變(包含增加和刪除記錄),然後於單一操作中進行所有改變的傳送回主機端。若決定採用Client-Side指標,則樂觀批次鎖定是最有效率的模式,因為它減少網路傳送。然而,得有機制來處理衝突(例如,當兩個使用者更新相同的記錄),其不是個不重要的工作。

關於更多鎖定的各種類型的相關資訊,請參閱第十四章的 〈鎖定機制〉 一節。

讀取與修正欄位值
 

開啟Recordset的最終目的是去讀取它的行列值,且可能會需要修改它們。Recordsets只准許讀取和寫入現行記錄的值,所以需要瀏覽Recordset以便存取所有感興趣的記錄。

透過Fields集合可讀取現行記錄欄位的值。藉由傳遞數字索引或者欄位的名稱,可指定感興趣的欄位:

' Print the names and values of all the fields in the Recordset.
Dim i As Integer
For i = 0 To rs.Fields.Count _ 1    ' The Fields collection is zero-based.
    Print rs.Fields(i).Name & " = " & rs.Fields(i).Value
Next

也可使用 For Each 敘述來找出所有記錄。可省略 Value 屬性,因為對於Field物件而言,它是預設屬性。

Dim fld As ADODB.Field
For Each fld In rs.Fields
    Print fld.Name & " = " & fld
Next

與DAO和RDO不同,ADO並不支援Edit方法,只需要指派新值給要修改的Field物件,便開始進行更新。此外,不需要明確地執行 Update 方法,因為當移動到Recordset的另一筆記錄時,ADO會自動執行。這些特性簡化了讀取和更新Recordset所有記錄的程式碼結構。

' Convert the contents of the LastName field to uppercase.
rs.MoveFirst
Do Until rs.EOF
    rs("LastName") = UCase$(rs("LastName"))
    rs.MoveNext
Loop

藉著查詢 EditMode 屬性,可得知Recordset的編輯狀態,其傳回值為:

說明
0-adEditNone 沒有編輯動作在執行中
1-adEditInProgress 一個以上的欄位已被修改,但新值尚未儲存
2-adEditAdd 新紀錄已新增,但尚未存到資料庫內
3-adEditDelete 現行記錄已被刪除

設定與取得Recordset內的位置
 

有許多屬性可幫助我們瞭解現在在Recordset的何處,以便關閉或開啟特性的動作,或設定書籤好快速地回到之前所拜訪的記錄。這類屬性中最長使用的莫過於 EOF 了;當現行記錄的指標位於Recordset最後一筆之後時,此屬性傳回True。當在Recordset的所有記錄中循環時,一般會使用此屬性:

' Count all employees hired before January 1, 1994.
rs.MoveFirst
Do Until rs.EOF
    If rs("HireDate") < #1/1/1994# then count = count + 1
    rs.MoveNext
Loop

BOF 是個類似的屬性。當記錄指標是在Recordset的第一筆之前時,此屬性傳回True。了解 EOF 和 BOF 屬性的值是很重要的:當其中一個傳回True時,Recordset的大部分方法和屬性會傳回錯誤,因為沒有現行記錄。例如,如果現行記錄是在Recordset的第一筆之前或者最後一筆之後,無法取得欄位值。如果 BOF 和 EOF 屬性都為True,則表示Recordset是空的。

Bookmark 屬性可取得表示現行記錄的Variant形態值;藉著重新指定相同值給 Bookmark 屬性,等會可簡單地移回到這筆記錄,以下為程式碼範例:

Dim mark As Variant
mark = rs.Bookmark              ' Remember where you are.
rs.MoveLast                     ' Move to the last record.
rs("HireDate") = #12/10/1994#   ' Assign a new value to the HireDate field.
rs.Bookmark = mark              ' Return to the marked record.

ADO書籤於內部是存為Double形態值。即使它們是數值,但不該假定它們可如同數字般進行比較。唯一對書籤有意義的算術運算為測試相等,如底下程式碼:

' Print the names of the employees who were hired on the same
' day as (or later than) the employee whose record is current in the Recordset.
Dim mark As Double, curHireDate As Date
mark = rs.Bookmark: curHireDate = rs("HireDate")
rs.MoveFirst
Do Until rs.EOF
    If rs.Bookmark <> mark Then
        ' Don't consider the current employee.
        If rs("HireDate") >= curHireDate Then Print rs("LastName")
    End If
    rs.MoveNext
Loop
' Move the record pointer back to the record that was current.
rs.Bookmark = mark

此外,只在書籤來自相同的Recordset物件或來自被複製Recordset時,其才可進行相等測試(稍後會有 Clone 方法的說明)。其他情況下,不該比較兩個不同Recordset物件的 Bookmark 屬性,儘管它們是指向在相同資料庫的同一個資料集。更多關於比較書籤的資訊,請參閱於稍後的 〈導覽Recordset〉 節的 CompareBookmark 方法。

RecordCount 唯讀屬性傳回Recordset的記錄筆數。根據資料庫引擎、提供者和Recordset類型,此屬性也可能傳回-1。例如此屬性不支援forward-only Recordset。如果此屬性被支援,讀取屬性值會強迫ADO執行隱含的 MoveLast 方法,所以如果使用在大的Recordset時,此動作會增添許多負荷。

AbsolutePosition 屬性設定或傳回相對於Recordset中現行記錄的順序之長變數值(第一筆傳回1;最後一筆傳回RecordCount)。也可傳回下列其中之一:-1- adPosUnknown(位置不明)、-2-adPosBOF(BOF狀態)或-3-adPosEOF(EOF狀態)。

永不使用此屬性來取代記錄號碼,或更糟地取代 Bookmark 屬性,因為當有記錄新增或刪除時,AbsolutePosition 屬性值會更改。大部分使用此屬性的理由是要提供捲軸或Slider控制項讓使用者可在Recordset中快速移動。於此情況下,應要設定捲軸的 Min 屬性為1和 Max 屬性為rs.RecordCount,然後在捲軸的 Change 或 Scroll 事件程序增加下列程式碼:

Private Sub HScrollBar1_Change()
    On Error Resume Next
    rs.AbsolutePosition = HScrollBar1.Value
End Sub

記住捲軸的 Max 值不能超過32767;如果必須處理比32767筆數還多的記錄,不是得改變捲軸刻度,要不就是使用Slider控制項。每個Recordset被細分成頁,每頁可以包含固定數令的記錄(最後一頁除外,它只有部分被填滿)。PageSize屬性傳回每頁記錄的數量,而 PageCount 屬性傳回Recordset的頁數。AbsolutePage 屬性設定或傳回目前記錄的頁碼,此屬性於概念上與 AbsolutePosition 屬性類似 (也支援相同的負值來表示位置不明,BOF和EOF狀態),只是其使用頁碼代替記錄編號。當實作進階策略以便緩衝從資料庫讀取的記錄時,它是非常有用的。

排序和篩選記錄
 

藉著指定欄位清單給 Sort 屬性,可排序Recordset的記錄,如以下範例:

' Sort the Recordset on the LastName and FirstName fields.
rs.Sort = "LastName, FirstName"

第一個欄位名稱是主要的排序索引鍵,第二個欄位名稱是第二個排序索引,以此類推。預設上,記錄是依據所選擇的欄位由小到大來排序;然而,藉著使用DESC語法也可選擇為由大到小的排序:

' Sort in descending order on the HireDate field. (The employees hired
' most recently should be listed first.) 
rs.Sort = "HireDate DESC"

說明

說明文件錯誤地說明應該使用 ASCENDING 和 DESCENDING 語法。然而,事實證明其造成編號3001的錯誤。此錯誤應在未來ADO版本中被修復。


此屬性不會影響在資料來源的記錄順序,但是會影響在Recordset的記錄順序。藉著指定空字串給此屬性可恢復為原始順序。筆者發現 Sort 方法只能在Client-Side Static指標上運作,至少對於ODBC、Microsoft Jet和SQL Server的OLE DB供給者而言是這樣的。如果針對非索引的欄位進行排序的話,ADO會為它們建立一個暫時的索引,且當關閉Recordset或者指定空字串給 Sort 屬性時,會刪除該索引。

藉由 Filter 屬性可篩選在Recordset內的記錄。可指定三種類型值給此屬性:SQL查詢字串、書籤陣列、或為一個指出在Recordset中哪一個記錄應該出現的常數。大部分使用此屬性的方法是將SQL字串設定給此屬性。此字串跟SELECT命令中的WHERE子句相類似,但是必須省略WHERE。下面是其範例:

' Filter out all employees hired before January 1, 1994.
rs.Filter = "HireDate >= #1/1/1994#"
' Include only employees born in the 1960s.
rs.Filter = "birthdate >= #1/1/1960# AND birthdate < #1/1/1970#"
' Filter in only employees whose last names start with the letter C.
rs.Filter = "LastName LIKE 'C*'"

可使用比較運算子(<、<=、>、>=、=、<>)和LIKE運算子,支援*與%,但只能在字串參數的尾端。亦可使用AND和OR邏輯運算子,連接較簡單的陳述句,但不能做其他的運算(例如字串連結)。使用插入與來對簡單的語法進行分群。如果某欄位名稱包含空白,必須以中括號將之括起來。如果提供者支援篩選,可在Server-Side指標下使用 Filter 屬性;在其他情況下,則應使用Client-Side指標。因為是由ADO進行過濾,所以應該遵循ADO的語法規則;例如:日期值必須以#符號括起來、字串則須用單引號括起來、若字串內要含有單引號,則須再加上一個單引號。(提示:使用 Replace 函數可快速備好此字串)。

如果要篩選無法透過簡單的SQL字串來指定的記錄時,可指定書籤陣列給 Filter 屬性:

' Filter out those employees who were hired when they were over age 35.
ReDim marks(1 To 100) As Variant
Dim count As Long
' Prepare an array of bookmarks. (Assume that 100 bookmarks are enough.)
Do Until rs.EOF
    If Year(rs("HireDate")) - Year(rs("BirthDate")) > 35 Then
        count = count + 1
        marks(count) = rs.Bookmark
    End If
    rs.MoveNext
Loop
' Enforce the new filter using the array of bookmarks.
ReDim Preserve marks(1 To count) As Variant
rs.Filter = marks

最後,指定 Filter 屬性為底下列舉常數中的某一個:

說明
0-adFilterNone 移除現在的篩選(與指定空字串相同)
1-adFilterPendingRecords 於批次更新模式下,只檢視已修改但尚未送至伺服器的記錄
2-adFilterAffectedRecords 檢視被最近的Delete、Resync、UpdateBatch或CancelBatch方法所影響的記錄
3-adFilterFetchedRecords 只檢視在客戶端快取區的記錄
5-adFilterConflictingRecords 於批次更新模式下,只檢視更新失敗的資料

設定 Filter 屬性為2-adFilterAffectedRecords值是唯一可看到已被刪除之記錄的方法。

其他屬性
 

MarshalOption 屬性影響如何傳遞資料列回伺服器。此屬性值可為: 0-adMarshalAll(ADO傳送所有資料列回伺服器,此為預設值),或者1-adMarshalModifiedOnly(ADO只傳送被修改的紀錄)。此屬性只在client端的ADOR Recordsets有效,其在第19章的 〈遠端資料服務(Remote Data Services)〉 會說明。

Status 屬性是位元欄位值,在批次更新運算或者大量的運算完成後,Status屬性傳回現行記錄的狀態。可使用表13-2的常數個別測試每個位元。

唯一尚未說明的Recordset屬性是 StayInSync,其應用於階層式Recordset 物件的子Recordsets。為了瞭解此屬性的作用,必須考慮階層式Recordset含有包含子Recordset物件的Field物件。預設情況下,當父Recordset的記錄指標移到另一個紀錄時,ADO會自動更新這些子Recordset。大多數情況下只需要使用此預設行為,但有時會需要稍後再儲存子Recordset的內容,因此就需要將之從父Recordset中分離開來。藉由設定子Recordset的 StayInSync 為False,可分離父與子Recordset。另一個達成相同結果的方法是使用 Clone 方法來建立子Recordset的複製:如果使用此方法,當父Recordset移往其他筆記錄時,複製出來的Recordset將不會更新。關於此更多的訊息,請參閱第十四章 〈階層式Recordset〉 節。

方法
 

Recordset物件包含一些方法。再次地,筆者將依據各方法的作用來分類描述。

常數 說明
AdRecOK 0 記錄成功被更新
adRecNew 1 此記錄為新的
adRecModified 2 記錄已被修改
adRecDeleted 4 記錄已被刪除
adRecUnmodified 8 記錄尚未被修改
adRecInvalid &H10 由於記錄的書籤是不合法的,所以記錄未儲存
adRecMul小密訣leChanges &H40 由於記錄會影響多筆記錄的,所以記錄未儲存
adRecPendingChanges &H80 由於記錄正等待被新增,所以其尚未改變
adRecCanceled &H100 由於動作被取消,所以記錄未儲存
adRecCantRelease &H400 由於以存在的記錄被鎖定,因此記錄未儲存
adRecConcurrencyViolation &H800 由於樂觀衝突,此記錄未儲存
adRecIntegrityViolation &H1000 由於記錄違反了一致性,記錄未儲存
adRecMaxChangesExceeded &H2000 因為有太多未定的改變,記錄未儲存
AdRecObjectOpen &H4000 由於與已開啟的物件發生衝突,記錄未儲存
adRecOutOfMemory &H8000 由於記憶體不足,記錄未儲存
adRecPermissionDenied &H10000 由於使用者權限不足,記錄無法儲存
adRecSchemaViolation &H20000 由於記錄與資料庫結構不符,無法儲存
AdRecDBDeleted &H40000 此記錄已從資料庫中刪除
表13-2 用來測試 Status 屬性的常數。

開啟和關閉Recordset
 

如果讀取在Recordset的資料,首先必須用 Open 方法開啟Recordset:

Open [Source], [ActiveConnection], [CursorType], [LockType], [Options]

Open 方法的參數與其同名屬性的意義相同:Source 是資料表的名稱或為預存函數、SQL查詢、或是對ADO Command物件的引用;ActiveConnection 是對ADO Connection物件的引用或為指定提供者和資料來源的連結字串;CursorType 決定要建立的指標類型(Forward-Only、Static、Keyset或Dynamic)。LockType 是要採取的鎖定類型(唯讀、悲觀的、樂觀的、或樂觀批次)。Options 是唯一與 Recordset 屬性不同的參數,它說明傳定給 Source 參數的哪種ADO,且可為下列常數之一:

說明
1-adCmdText 文字SQL查詢
2-adCmdTable 資料庫之資料表
4-adCmdStoredProc 預存函數
8-adCmdUnknown 未定義;由Provider來決定其正確的形態
256-adCmdFile 固定的Recordset
512-adCmdTableDirect 直接開啟的資料表

即使在大部分情況下,即使未提供資訊,提供者還是可瞭解Recordset來源是什麼,但通常藉著指定正確的值給此參數會加速 Open 方法。

所有參數都是可選擇的。然而,如果不提供足夠的資訊,ADO並無法開啟Recordset。例如,如果已指定值給 Source 屬性,則可省略 Source 參數,如果已指定值給 ActiveConnection 屬性或者如果使用一個ADO物件當做Recordset的來源(在例子裡,ActiveConnection 參數繼承自Command物件)時,則省略 ActiveConnection 參數。若省略第三或第四個參數,藉著預設值,Open 方法會建立Forward-Only、唯讀Recordset,這是ADO所支援最有效率的Recordset類型。在 Open 方法中無法指定指標位置,如果要建立Client-Side指標,在開啟Recordset前必須指定adUseClient常數給CursorLocation屬性,下面例子說明 Open 方法的動作。

' Edit this constant to match your directory structure.
Const DBPATH = "C:\Program Files\Microsoft Visual Studio\VB98\NWind.mdb"
' All the following examples use these variables.
Dim cn As New ADODB.Connection, rs As New ADODB.Recordset
Dim connString As String, sql As String
ConnString = "Provider=Microsoft.Jet.OLEDB.3.51;Data Source=" & DBPATH

' Open the Recordset using an existing Connection object.
cn.Open connString
rs.Open "Employees", cn, adOpenStatic, adLockReadOnly, adCmdTable

' Open the Recordset using a Connection object created on the fly.
' This creates a forward-only, read-only Recordset.
rs.Open "Employees", connString, , , adCmdTable
' After the Recordset has been opened, you can query the properties
' of the implicit Connection object.
Print "Current Connection String = " & rs.ActiveConnection.ConnectionString

' Select only the employees who were born in the 1960s or later.
sql = "SELECT * FROM Employees WHERE BirthDate >= #1/1/1960#"
rs.Open sql, connString, , , adCmdText

可開啟先前使用 Save 方法儲存在硬碟的記錄。於此例子中,Open 方法的第一個參數是完整的名稱和檔案路徑,且應該傳遞adCmdFile常數給 Options 參數。

Options 參數支援兩個以上非同步動作的。值16-adAsyncExecute以非同步方式來執行查詢:控制項立刻回到應用程式,且ADO繼續去填充Recordset直到區域端的快取衝板資料為止。值32-adAsyncFetch說明在區域端快取填滿資料後,其應要非同步地時地擷取剩下的記錄。當所有記錄皆已取得時,ADO會引發 FetchComplete 事件。

在任何時刻,可藉由 Cancel 方法來取消非同步動作。如果沒有非同步動作未定的話,此方法不會做任何事也不會有錯誤產生。

當完成Recordset的運作後,應要使用 Close 方法來關閉之。此方法無須任何參數。當沒有變數指向Recordset時,ADO會自動關閉它。當Recordset關閉後,ADO會解除所有鎖定與釋放給指標用的記憶體空間。若有編輯動作在進行中,是不能關閉Recordset的(亦即,若修改一個以上的欄位值,且未確定其改變)。藉著使用相同或不同的 Source、CursorType、MaxRecords、CursorPosition 和 LockType 屬性,可重新開啟關閉的Recordset。(當Recordset是開啟時,這些屬性是唯讀的)。

也可藉著 Clone 方法來建立一個存在的Recordset的複製版本。

Dim rs2 As ADODB.Recordset
Set rs2 = rs.Clone(LockType)

可選擇的 LockType 參數說明對於此新的Recordset要採用何種鎖定。複製的記錄只能用與原先記錄相同的鎖定類型來開啟(想要如此,只需省略參數即可),或以唯讀模式開啟(傳以adLockReadOnly常數)。複製Recordset比起建立相同資料來源的另一個Recordset而言是較有效率的。在一個Recordset的任何修改會立即於所有其複製版本中顯現,不論其指標形態,但是所有在群組裡的Recordsets可各自捲動與關閉。如果對原先的Recordset執行 Requery 方法,其複製版本則不再同步化。(然而反過來不成立,如果Request複製版本,它們仍會與原本的Recordset同步化)。記住只有支援書籤的Recordset可被複製,然後可比較定義在Recordset與其複製版本的書籤。

更新Recordset
 

ADO提供兩個方法來重新發佈Recordset,而無須關閉或再開啟它。Requery 方法會再執行Recordset查詢。當沒有使用Command物件,而以相對於SQL Server資料庫的參數化查詢時此方法特別有用,這是因為其告訴ADO再使用當Recordset第一次被開啟,SQL Server所建立的暫時預存函數。Requery 方法接受adAsyncExecute選項來進行非同步查詢。當查詢完成時,會引發RecordsetChangeComplete 事件。Requery 方法雖可再執行查詢,但不能修改任何影響指標類型的屬性(CursorType、CursorLocation、LockType 等)這是因為當Recordset開啟時,這些屬性是唯讀的。為了改變這些屬性,必須關閉然後再開啟Recordset。

Resync 方法更新資料庫的Recordset,而不需要實際再執行查詢。其語法如下:

Resync [AffectRecords], [ResyncValues]

AffectRecords 區分哪個記錄應該被重新更新,且可以為下列常數之一:

說明
1-adAffectCurrent 只更新現行記錄
2-adAffectGroup 更新符合現行Filter屬性的記錄,其應被指定所支援的值
3-adAffectAll 更新整個Recordset(預設值)

ResyncValues 可以是下列值之一:

說明
1-adResyncUnderlyingValues 從資料庫讀取最新的值,且將其存於Filed物件的UnderlyingValue屬性
2-adResyncAllValues 讀取最新的值,且將他們存於Field物件的Value屬性(預設值)

這兩個選項的效果是完全不同:adResyncUnderlyingValues保存舊的資料且不會取消未定的改變;adResyncAllValues取消未定的改變(就好像執行 CancelBatch 方法般)。

因為 Resync 方法不會再執行查詢,同時也就不會看到由其他使用者增加的新記錄。當要確定是對最新值在運作時,這方法對於forward-only或Static指標特別有用。任何在同步化過程中的衝突-例如,其他使用者已經刪除一筆記錄-會用一至多個警告來填入Errors集合。當使用Client-Side指標時,此方法只對可更新的Recordsets有效。

取得資料
 

讀取現行記錄的值,可簡單地查詢Fields集合,如下:

' Print employee's first and last name.
Print rs.Fields("FirstName").Value, rs.Fields("LastName").Value

因為 Fields 是Recordset物件的預設屬性,因此可省略它並單只藉由其名稱或索引來存取欄位。同樣地,可省略 Value 屬性,因為它是Field物件預設屬性。

Print rs("FirstName"), rs("LastName")

藉著反覆於Fields集合中,可顯示現行記錄所有欄位的值。可在 For...Next 迴圈中使用欄位的索引或在 For Each...Next 迴圈中使用Field物件變數:

' The first method uses a regular For...Next loop.
For i = 0 To rs.Fields.Count _ 1
    Print rs.Fields(i).Name & " = " & rs(i)
Next

' The second method uses a For Each...Next loop.
Dim fld As ADODB.Field
For Each fld In rs.Fields
    Print fld.Name & " = " & fld.Value
Next

ADO也提供更有效率方法來取得資料。GetRows 方法傳回一個二維的陣列變數,每一行對應Recordset中的一筆記錄,且每一列對應到記錄中的欄位。此方法的語法如下:

varArray = rs.GetRows([Rows], [Start], [Fields])

Rows 是要讀取記錄的數量;如果想要取得Recordset所有記錄,可用-1或省略此參數。Start 是指出第一個被讀取記錄的書籤;也可以是下列列舉常數中的一個:0-adBookmarkCurrent(目前記錄)、1-adBookmarkFirst(第一筆記錄)、或2-adBookmarkLast(最後記錄)。

Fields 是可選擇的欄位名稱陣列,其用來限制要讀取的資料量。(也可指定單一欄位名稱、單一欄位索引、或者一個欄位索引陣列)。當設定 Rows 為少於Recordset記錄筆數時,第一筆未讀取的記錄變成現行記錄。若省略 Rows 參數或設定為-1-adGetRowsRest或大於未讀取的筆數時,GetRows 方法會讀取所有記錄並讓Recordset在EOF狀態,而不會產生任何錯誤。

當處理目的變數陣列的資料時,應該記得資料儲存方式是有點相反的感覺:陣列中第一維定義Recordset的欄位(資料行),第二維定義Recordset的資料列。以下有個載入Recordset內所有記錄某三個欄位的範例:

Dim values As Variant, fldIndex As Integer, recIndex As Integer
values = rs.GetRows(, , Array("LastName", "FirstName", "BirthDate"))
For recIndex = 0 To UBound(values, 2)
    For fldIndex = 0 To UBound(values)
        Print values(fldIndex, recIndex),
    Next
    Print
Next

GetRows 方法通常比一次讀一筆記錄的迴圈要來得快些,但使用這方法時,必須確定Recordset未包含太多記錄;否則,會很容易以一個非常大的變數陣列來填滿所有記憶體。基於相同的原因,得小心不要包括任何BLOB(Binary Large Object)或CLOB(Character Large Object)欄位;若如此做的化,應用程式一定會爆掉,特別是對於較大的Recordset而言。最後,記住此方法傳回的變數陣列是以0為基底的;傳回記錄的筆數是UBound(values,2)+1,傳回欄位數是UBound(value, 1)+1。

GetString 方法跟 GetRows 是類似的,不過其傳回以單一字串形式存在的多重記錄。GetString 語法如下:

GetString([Format], [NumRows], [ColDelimiter], [RowDelimiter], [NullExpr])

Format 是結果的格式。GetString 還可能支援更多格式,但是目前唯一支援的格式是2- adClipString,所以實際上沒有任何選擇。NumRows 是要取得的列數。(使用-1或省略此參數來讀取所有剩下的記錄。)ColDelimiter 是行的分隔字元(預設為Tab字元)。RowDelimiter 為記錄的分隔字元(預設為換行字元)。NullExpr 是用來表示Null欄位的字串(預設為空字串)。文件中說明只有當Format= adClipString時,最後三個參數才可使用,但是這警告沒有多大意義,因為(如之前所提)此格式是目前唯一支援的。以下有個例子,其透過 GetString 方法來將資料匯出成以分號分隔的文字檔:

Dim i As Long
Open "datafile.txt" For Output As #1
For i = 0 To rs.Fields.Count _ 1          ' Export field names.
    If i > 0 Then Print #1, ";";
    Print #1, rs.Fields(i).Name;
Next
Print #1, ""
rs.MoveFirst                              ' Export data.
Print #1, rs.GetString(, , ";", vbCrLf);  ' Don't add an extra CR-LF here.
Close #1

GetString 方法不允許只匯出欄位的子集合,也不允許修改匯出欄位的順序。如果需要這些額外的功能,應該使用 GetRows 方法且自行建立結果字串。

瀏覽Recordset
 

當開啟一個Recordset時,現行記錄指標指到第一筆記錄,除非Recordset是空的(於此情況下,BOF 和 EOF 屬性都傳回True)。為了讀取和修改其他記錄的值,則必須使記錄成為目前記錄,通常需藉著執行Recordset物件提供的 Movexxxx 方法。MoveFirst 移到Recordset的第一筆記錄,MoveLast移到最後一筆記錄,MovePrevious 移到前一個記錄,而 MoveNext 移到下一筆記錄。一般來說,可提供四個按鈕給使用者,用來瀏覽Recordset。當BOF為True時,執行 MovePrevious 方法,或當 EOF 為True時執行 MoveNext 方法會引起錯誤;所以,在移動到前一筆或下一筆記錄前,必須捕捉這些條件:

Private Sub cmdFirst_Click()
    rs.MoveFirst
End Sub

Private Sub cmdPrevious_Click()
    If Not rs.BOF Then rs.MovePrevious
End Sub

Private Sub cmdNext_Click()
    If Not rs.EOF Then rs.MoveNext
End Sub

Private Sub cmdLast_Click()
    rs.MoveLast
End Sub

MoveFirst 和 MoveNext 方法通常是用在迴圈,以便反覆找出Recordset內的所有記錄的迴圈中,如下範例所示:

rs.MoveFirst
Do Until rs.EOF
    total = total + rs("UnitsInStock") * rs("UnitPrice")
    rs.MoveNext
Loop
Print "Total of UnitsInStock * UnitPrice = " & total

ADO也提供一般的 Move 方法,語法如下:

Move NumRecords, [Start]

NumRecords 是指定跳過Recordset的尾端(若為正數)或略過開始(若為負數)的記錄筆數,其形態為Long。此移動相對於被 Start 參數所指定的記錄,其可為書籤或下列列舉的常數之一:

說明
0-adBookmarkCurrent 現行記錄
1-adBookmarkFirst Recordset中的第一筆記錄
2-adBookmarkLast Recordset中的最後一筆記錄

如下面所見,Move 方法包含四個之前提過的 Movexxxx 方法的功能。

rs.Move 0, adBookmarkFirst           ' Same as MoveFirst
rs.Move _1                           ' Same as MovePrevious
rs.Move 1                            ' Same as MoveNext
rs.Move 0, adBookmarkLast            ' Same as MoveLast
rs.Move 10, adBookmarkFirst          ' Move to the tenth record.
rs.Move -1, adBookmarkLast            ' Move to the next to the last record.
rs.Move 0                              ' Refresh the current record.

若指定一個指向在第一筆記錄前的記錄的負相對值,BOF 屬性會變成True,且不會引發錯誤。同樣地,如指定一個指向最後一個記錄之後的正相對值時,EOF 屬性會被設為True,且也沒有錯誤發生。有趣地,即使是forward-only Recordset,也可指定負相對值:如果目的記錄仍在區域端快取區,則沒有錯誤發生。(forward-only Recordset不能使用MovePrevious,不論先前的記錄是否在快取區中)。

也可使用 Bookmark 和 AbsolutePosition 屬性來瀏覽Recordset。ADO也提供用來比較來自相同Recordset或來自複製Recordset書籤的 CompareBookmarks 方法。此方法語法如下:

result = CompareBookmarks(Bookmark1, Bookmark2)

Result可接受以下其中之一的值:

說明
0-adCompareLessThan 第一個書籤指向第二個書籤指向記錄之前的記錄
1-adCompareEqual 此兩個書籤指向相同記錄
2-adCompareGreaterThan 第一個書籤指向第二個書籤指向記錄之後的記錄
3-adCompare注意事項qual 此兩個書籤指向不同的記錄,但提供者無法確定哪個在前.
4-adCompareNotComparable 書籤無法被比較

更新、新增和刪除記錄
 

ADO不同於DAO和RDO,於ADO中,Update實際上是不需要的:修改記錄所要做的是指定新值給一個或多個Field物件,然後移到另一筆記錄去。ADO的 Update 方法支援一次更新多重欄位的能力,使用下列句法:

Update [fields] [, values]

fields 是個包含單一欄位名稱的變數、欄位索引或欄位名稱或索引的陣列。Values是個包含單一值或值陣列的變數。這些參數是可選擇的,但是不能只省略兩個中的一個:若有提供的話,它們必須包含相同數目的參數。下面例子說明如何此語法更新多重欄位:

' Update four fields in one operation.
rs.Update Array("FirstName", "LastName", "BirthDate", "HireDate"), _
    Array("John", "Smith", #1/1/1961#, #12/3/1994#)

因為如果現行記錄中的一個或多個欄位已經被修改,則更新動作會自動地完成,所以ADO還提供 CancelUpdate 方法來取消這樣的改變,並還原現行記錄為未修改樣子。可一起使用 Update 和 CancelUpdate 方法給予使用者機會去確認或取消對現行記錄的改變:

If rs.EditMode = adEditInProgress Then
    If MsgBox("Do you want to commit changes?", vbYesNo) = vbYes Then
        rs.Update
    Else
        rs.CancelUpdate
    End If
End If

用 AddNew 方法可以增加新紀錄到Recordset。此方法類似於 Update 方法,其支援兩種語法格式,有或沒有參數。如果沒有傳遞參數,會在Recordset尾端建立一筆新記錄,並應該透過Fields集合指定值給各欄位:

rs.AddNew
rs("FirstName") = "Robert"
rs("LastName") = "Doe
rs("BirthDate") = #2/5/1955#
rs.Update

在 AddNew 方法後並不需要明確的 Update 方法-任何 Movexxxx方法將會進行更新運作。在第二個語法格式中,須傳遞欄位清單和值清單給 AddNew 方法;於此種情況下,不需要更新,因為值會立刻被寫入資料庫:

' This statement has the same effect as the previous code snippet.
rs.AddNew Array("FirstName", "LastName", "BirthDate"), _
    Array("Robert", "Doe", #2/5/1955#)

用 Update 方法確認改變後,剛新增的記錄成為現行記錄。如果執行第二個 AddNew 方法,會自動確認之前新增的記錄,如同執行 Movexxxx 方法般。根據指標的類型,已經新增的記錄並未立刻顯示在Recordset上是有可能的,因此必須執行 Requery 方法才能看到。

藉著執行 Delete 方法可刪除現行記錄。此方法接受一個可選擇的參數:

rs.Delete [AffectRecords]

如果 AffectRecords 是1-adAffectCurrent或被省略的話,只有現行記錄會被刪除。當刪除一筆記錄時,其仍為現行記錄,但是它不能再被存取,所以最好移到另一個記錄去:

rs.Delete
rs.MoveNext
If rs.EOF Then rs.MoveLast

藉著指定一個列舉常數給 Filter 屬性,可刪除一群記錄,然後執行AffectRecords 參數設為2-adAffectGroup的 Delete 方法:

' After a batch update attempt, delete all the records that failed
' to be transferred to the server.
rs.Filter = adFilterConflictingRecords
rs.Delete adAffectGroup
rs.Filter = adFilterNone        ' Remove the filter.

如果要讓使用者有機會取消刪除的話,應要將刪除動作置於交易內。

搜尋記錄
 

Find 方法提供一個簡單方法來移動到符合Recordset搜尋條件的一筆記錄。此方法的語法如下:

Find Criteria, [SkipRecords], [SearchDirection], [Start]

Criteria 是包含查詢條件的字串,查詢條件包括欄位名稱,跟隨以運算子和數值。所支援的運算子有=(等於)、<(小於)、>(大於)及LIKE(相似的)。數值可以是被單引號括起來的字串、數字或由#字元所括起來日期值。SkipRecord 是個可選擇的數,指出在開始搜尋之前有多少筆記錄應該略過:正值向前略過(朝向Recordset的最後一筆),而負值為向後略過(朝向Recordset第一筆)。SearchDirection 指出搜尋進行的方向;可為1-adSearchForward(預設值)或-1-adSearchBackward值。Start是個可選擇的書籤,其指定搜尋應該從哪個記錄開始(預設值是現行記錄)。

在大部分案例中,可省略除了第一個外的所有參數,其搜尋結果是從現行記錄開始(包括),然後朝向資料庫的最後一筆。如果搜尋成功,符合搜尋規範的記錄則變成目前記錄;若搜尋失敗,則現行記錄會成為Recordset的最後一筆記錄之後(若為Search = adSearchBackward,則在第一筆之前)。當搜尋發現一個符合情況後要再重新開始時,傳遞非零的值給 SkipRecord 參數是必須的,如下面程式碼:

' Search all the employees who were hired after January 1, 1994.
rs.MoveFirst
rs.Find "HireDate > #1/1/1994#"
Do Until rs.EOF
    Print rs("LastName"), rs("BirthDate"), rs("HireDate")
    ' Search the next record that meets the criteria, but skip the current one. 
    rs.Find "HireDate > #1/1/1994#", 1
Loop

LIKE運算子接受兩個廣域符號:*(星號)符合零個或者更多的字元,而_(底線)只剛好符合一個字元。比較符號是不分大小寫的,且不被 Option Compare 指令所影響。以下是範例:

rs.Find "FirstName LIKE 'J*'"    ' Matches "Joe" and "John".
rs.Find "FirstName LIKE 'J__'"   ' Matches "Joe" but not "John".
rs.Find "FirstName LIKE '*A*'"   ' Matches "Anne", "Deborah", and "Maria".
rs.Find "FirstName LIKE '*A'"    ' This gives an error: a bug?

以批次模式更新記錄
 

若開啟adLockBatchOptimistic選項的Recordset,之前關於記錄更新的規則都沒有用。當使用樂觀批次更新作業時,實際上是運作在客戶端工作站尚的指標。即使連接到伺服器的連線不再存在時,還是可以讀取指標,且可修改記錄而不確認修改到伺服器(至少不是立刻地)。在樂觀批次模式中,隱含或明確的 Update 方法只影響區域端指標,而非真正的資料庫。這可幫助維持網路運輸到最小,並大大地改善整體性能。

當準備確認改變到伺服器上的資料庫時,則執行 UpdateBatch 方法,語法如下:

UpdateBatch [AffectRecords]

指定 AffectRecords 參數為下列常數中之一:

說明
1-adAffectCurrent 只更新現行記錄
2-adAffectGroup 更新所有滿足現行Filter屬性的記錄
3-adAffectAll 更新所有Recordset中已修改的記錄(預設值)
4-adAffectAllChapters 更新階層式Recordset的所有Chapter

AdAffectAll設定在ADODB類型函式庫中是隱藏的。若在編輯模式下執行UpdateBatch 方法,ADO會確認現行記錄的修改,然後執行批次更新。

Visual Basic文件說明若有衝突且一至多個記錄不能成功地更新時,ADO會已警告來填入Errors集合,但在應用程式中並不會產生錯誤。只有當記錄更新失敗時,ADO才會產生錯誤。然而,某些測試證明,當有衝突的記錄時,錯誤編號&H80040E38,「錯誤發生」會傳回給應用程式。可設定 Filter 屬性為adFilterConflictingRecords值來察看哪筆記錄沒有更新成功。

使用 CancelBatch 方法可取消批次更新,語法如下:

CancelBatch [AffectRecords]

此處的AffectRecords於在 UpdateBatch 方法的AffectRecords有著相同的意義。如果Recordset不以adLockBatchOptimistic選項來開啟,除了1-adAffectCurrent之外的任何其他值皆會導致錯誤。若在編輯模式,CancelBatch方法首先會取消目前記錄的更新,然後再取消被 AffectRecords 參數所影響記錄的改變。在 CancelBatch 方法完成後,現行記錄的位置有可能成為未知數,所以應該使用 Movexxxx 方法或 Bookmark 屬性移到一筆有效的記錄。

當在庫戶端執行批次更新動作時,不需保持連接到資料庫的連線。事實上,設定Recordset的 ActiveConnection 屬性為Nothing,可關閉相對的Connection物件,讓使用者瀏覽和更新資料,然後當準備更新資料到資料庫時,再重建連結。關於更多關於批次更新的資訊,請參閱第十四章的 〈樂觀批次更新〉 乙節。

實作固定的Recordsets
 

ADO Recordset物件最詭異的特色之一是可以將ADO Recordset儲存為一般的磁碟檔案,然後當必要時開啟之。這特色在許多情況下是有利的-例如,當執行批次更新或當想要延緩Recordset進行時。甚至不需用原本儲存它之相同程式來重新開啟Recordset。例如,儲存Recordset到一個檔案,且稍後在下班時透過印表程式將之輸出到印表機。Save 方法是此能力的關鍵,語法如下:

Save [FileName], [PersistFormat]

第一個參數是Recordset要被儲存成的檔案名稱,第二個參數是Recordset要儲存成哪種格式。ADO 2.0只支援Advanced Data TableGram (ADTG),所以應該指定常數0-adPersistADTG或省略此參數。雖然 Save 方法的語法非常直覺,當運作於固定Recordsets時,其仍包括一些必須注意的細節。

一般而言,固定Recordset應該使用Client-Side指標,所以該改變CursorLocation屬性值,其預設值為adUseServer。然而,有些提供者支援使用Server-Side指標的能力。

  • 只有再第一次儲存Recordset到檔案時,才須要指定檔案名稱;在後續所有的儲存中,若存到相同檔案,則必須省略第一個參數。若未省略參數的化,會產生執行階段的錯誤。
     
  • 如果檔案早已存在,則會產生錯誤,所以在執行 Save 方法前應要測試檔案是否存在,且若必要的話,手動地刪除之。
     
  • Save 不會關閉Recordset,所以可繼續對其運作,並再藉著沒有 FileName 參數的 Save 方法來儲存最新的改變。只有當Recordset關閉時,檔案才會關閉;同時,其他應用程式還是可讀取此檔,但不能寫入此檔案。
     
  • 儲存到檔案後,可以指定不同檔案名稱來儲存到不同的檔案;然而,此運作不會關閉原先的檔案。直到Recordset關閉,此兩個檔案才會關閉。
     
  • 如果 Filter 屬性正在作用中,只有可見的記錄會被儲存。此一特性對於延緩於批次更新動作中更新失敗記錄的進行是有用的。
     
  • 如果 Sort 屬性不是空字串,記錄將會依順序來儲存。
     
  • 如果當非同步運作在進行時執行 Save 方法,直到非同步運作完成後,此方法才會回傳。
     
  • 在 Save 動作後,現行記錄將成為Recordset的第一個記錄。
     

當開啟固定Recordset時,應該在 Open 方法的 Option 參數中使用adCmdFile值:

' Save a Recordset to a file, and then close both the file
' and the Recordset.
rs.Save "C:\datafile.rec", adPersistADTG
rs.Close
'...
' Reopen the persistent Recordset.
rs.Open "C:\datafile.rec", , , , adCmdFile

說明

因為ADTG是二進位格式;無法簡單地編輯存成此格式的Recordset。ADODB類型函式庫已包括隱藏的常數1- adPersistXML,即使此常數在ADO 2.0不支援。好消息是ADO 2.1完全支援在XML格式的Recordset;這選擇是很誘人的,因為XML是文字檔格式,所以可用編輯工具編輯已儲存的檔案。


管理多重Recordset
 

若提供者支援多重查詢的話,則ADO Recordset亦支援 Source 屬性或 Open 方法的 Source 參數的多重查詢。藉著分號作為分隔字元來指定多重SELECT查詢,或甚至SQL動作查詢,如下說明:

Rs.Open "SELECT * FROM Employees;SELECT * FROM Customers"

當 Open 方法完成其執行時 ,Recordset物件包含第一個查詢的所有記錄,處理這些記錄的方式如同一般Recordset。當完成處理這些記錄時,使用 NextRecordset 方法取得來自第二個查詢的記錄:

Dim RecordsAffected As Long
Set rs = rs.NextRecordset(RecordsAffected)

參數是選擇性的:如果有指定的話,它應該是個Long形態的變數。此變數接收已被目前運算影響的記錄筆數(也可能是個沒有傳回Recordset的SQL命令)。雖然語法允許指定 NextResult 方法的結果給其他Recordset變數,但在筆者寫作之時,尚沒有提供者支援此功能,且Recordset的原始內容總是被丟棄。如果此功能成為有效時,指定每個Recordset物件給不同的物件變數且同時處理所有Recordset是可能的。

當使用多重Recordset作業時,這有些細節您必須知道:

  • 只當 NextRecordset 方法要求時,每個查詢才被執行;因此,在處理所有未定的命令前若關閉Recordset的話,相對的查詢或動作命令是永不被執行。
     
  • 如果一個傳回資料列的查詢不傳回任何記錄,則結果的Recordset是空的。藉著檢查 BOF 和 EOF 屬性是否都傳回True,可以測試這情況。
     
  • 如果未定的 SQL命令沒有傳回任何資料列,結果的Recordset將被關閉,透過 State 屬性可以測試這情況。
     
  • 當沒有命令是未決定時,NextRecordset 方法傳回Nothing。
     
  • 如果編輯動作正在進行時,是無法呼叫 NextResult 方法的;為了避免錯誤,首先應該執行 Update 或 CancelUpdate 方法。
     
  • 如果一個或多個SQL命令或查詢要求參數,應該用所有被要求的參數值來填滿Parameters集合,且其應以命令或查詢所期望的順序出現。
     
  • 提供者必須支援多重查詢。例如,Microsoft Jet資料庫的提供者就不支援。SQL Server的提供者似乎只有Static Client-Side指標或Server-Side的無指標Recordset才支援此功能。
     

當針對多重Recordsets作業時,這兒有個您可使用的程式範例:

Dim RecordsAffected As Long
rs.Open
Do 
    If rs Is Nothing Then 
        ' No more Recordsets, so exit.
        Exit Do 
    ElseIf (rs.State And adStateOpen) = 0 Then
        ' It was a non-row-returning SQL command.
        ...
    Else
        ' Process the Recordset here.
        ...
    End If
    Set rs.NextRecordset(RecordsAffected)
Loop

測試特性
 

並非所有類型的Recordsets都支援之前所提的特性。無須猜測哪一個特性是被支援或不被支援的,ADO Recordset物件有 Supports 方法,此方法接受位元參數且若Recordset支援參數所描述的特性時會傳回True。例如,透過下列程式碼可測試Recordset是否支援書籤:

If rs.Supports(adBookmark) Then currBookmark = rs.Bookmark

Supports 方法的參數可包括一個或多個列在表13-3的常數。若想要測試多個特性,並不需要陳述多個 Supports 方法。

If rs.Supports(adAddNew Or adDelete Or adFind) Then
    ' The Recordset supports the AddNew, Delete, and Find methods.
End If

不要忘記如果此方法傳回True,只能確定ADO支援要求的運作,OLE DB提供者未必支援。

常數 說明
adHoldRecords &H100 支援讀取更多的記錄或在未確定未定改變前,改變下個擷取位置
adMovePrevious &H200 支援MoveFirst與MovePrevious方法與Move 和GetRows
adBookmark &H2000 支援Bookmark屬性
adApproxPosition &H4000 支援AbsolutePosition與AbsolutePage屬性
adUpdateBatch &H10000 支援UpdateBatch與CancelBatch方法
adResync &H20000 支援Resync方法
adNotify &H40000 支援
adFind &H80000 支援Find方法
adAddNew &H1000400 支援AddNew方法
adDelete &H1000800 支援Delete方法
adUpdate &H1008000 支援Update方法
表13-3 Supports方法的參數

事件
 

ADO Recordset物件具有11個事件。這些事件可對Recordset進行完全的控制。當一個欄位或一筆記錄被修改,藉著寫這些事件的程式碼,可進行非同步查詢、捕抓到欄位或記錄被修改的那一刻,與當到達Recordset的尾端增加資料時。要觀察事件,ADO Workbench應用程式有著很大的幫助,因它會自動轉換所有列舉的常數為符號名稱。

資料取得類事件
 

在一個長的非同步作業期間,FetchProgress 事件是會週期性地被引發。可使用此事件來顯示進度狀態,說明記錄取得的百分比。

Private Sub rs_FetchProgress(ByVal Progress As Long, _
    ByVal MaxProgress As Long, adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset)

Progress 參數是到目前為止取得記錄的筆數。MaxProgress 是預期取得記錄的總數。adStatus 是常見的狀態參數。pRecordset 對應到引發此事件的Recordset物件(在Visual Basic中不需使用此參數,因為已有一個對Recordset的參考。)

當記錄取回完成時,ADO產生 FetchComplete 事件。如果 adStatus 參數等於adStatusErrorsOccurred,可透過 pError 參數查得此錯誤:

Private Sub rs_FetchComplete(ByVal pError As ADODB.error, _
    adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset)
End Sub

資料導覽類事件
 

每次現行記錄改變時,會引發 WillMove 事件,接著再引發 MoveComplete事件,如下列程式碼所示:

Private Sub rs_WillMove(ByVal adReason As ADODB.EventReasonEnum, _
    adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset)

adReason 參數說明此事件為何被產生。它可能是列在表13-4中的某個常數。除非 adStatus 被設為adStatusCantDeny,否則可藉由指派adStatusCancel值給 adStatus 來取消此動作。

當移動動作已經完成(或已被取消)時,MoveComplete 事件會引發:

Private Sub rs_MoveComplete(ByVal adReason As ADODB.EventReasonEnum, _
    ByVal pError As ADODB.error, adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset)

adReason 和 adStatus 參數 WillMove 事件內同名參數意義相同。如果 adStatus 等於adStatusErrorOccurred,則 pError 物件包含關於錯誤的訊息;否則的話,pError 是Nothing。藉由設定 adStatus 為adStatusUnwantedEvent,可取消進一步的訊息。

常數
1 AdRsnAddNew
2 AdRsnDelete
3 AdRsnUpdate
4 AdRsnUndoUpdate
5 AdRsnUndoAddNew
6 AdRsnUndoDelete
7 AdRsnRequery
8 AdRsnResynch
9 AdRsnClose
10 AdRsnMove
11 AdRsnFirstChange
12 AdRsnMoveFirst
13 AdRsnMoveNext
14 AdRsnMovePrevious
15 AdRsnMoveLast
表13-4 Recordset事件中adReason參數的值

當程式試圖移動到Recordset尾端時,如同 MoveNext 方法的結果般,有可能會導致 EndOfRecordset 錯誤事件:

Private Sub rs_EndOfRecordset(fMoreData As Boolean, _
    adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset)

當此事件發生時,ADO允許您增加一筆新紀錄至Recordset。若要善用此機會的優點,只要執行 AddNew 方法,用資料填滿Fields集合,然後設定 fMoreData 參數為True以便讓ADO知道您已增加新記錄。照例,藉著設定 adStatus 參數為adStatusCancel可取消此動作,除非 adStatus 參數包含adStatusCantDeny值。

更新類事件
 

任何時間,當ADO將要修改Recordset的一個或多個欄位時,都會引發WillChangeField 事件:

Private Sub rs_WillChangeField(ByVal cFields As Long, _
    ByVal Fields As Variant, adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset)

cFields 是將被修改的欄位數,而 Fields 是個Variant陣列,其包含一個或多個尚未改變的Field 物件。設定 adStatus 為adStatusCancel可取消此尚未更新的動作,除非其包含adStatusCancel值。

當更新運作完成,ADO會引發 FieldChangeComplete 事件,此事件接受相同的參數,再加上一個pError物件,其用來撿雜在這當中所產生的任何錯誤(若 adStatus 等於adStatusErrorOccurred時)。

Private Sub rs_FieldChangeComplete(ByVal cFields As Long, _
    ByVal Fields As Variant, ByVal pError As ADODB.error, _
    adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset)

當一個或多個記錄即將被改變時,ADO會引發 WillChangeRecord 事件。

Private Sub rs_WillChangeRecord(ByVal adReason As ADODB.EventReasonEnum, _
    ByVal cRecords As Long, adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset)

adReason 是列在表13-4的列舉常數中的一個,cRecords 是將被修改的記錄數,adStatus 是個可將之設定為adStatusCancel來取消運作的參數(除非 adStatus 參數包含adStatusCantDeny值)。

當更新運作完成,ADO會引發 RecordChangeComplete 事件。

Private Sub rs_RecordChangeComplete( _
    ByVal adReason As ADODB.EventReasonEnum, ByVal cRecords As Long, _
    ByVal pError As ADODB.error, adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset)

所有參數的意義與 WillChangeRecord 事件的同名參數一樣。如果 adStatus為adStatusErrorOccurred,可查詢 pError 物件得知發生了什麼事,且將 adStatus 設為adStatusUnwantedEvent可拒絕進一步的訊息告知。這兩個事件會因為 Update、UpdateBatch、Delete、CancelUpdate、CancelBatch 或 AddNew 方法而引發。在這事件中,Filter 屬性會被設為adFilterAffectedRecords且無法改變之。

當ADO欲執行將改變Recordset所有內容的動作時-例如 Open、Requery 和 Resync 方法-WillChangeRecordset 事件會被引發:

Private Sub rs_WillChangeRecordset( _
    ByVal adReason As ADODB.EventReasonEnum,
    adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset)

adReason 是列在表13-4中的某常數值,adStatus 意義與以往相同。如果此參數不等於adStatusCantDeny,則將參數設為adStatusCancel可取消運作。

當更新運作完成,ADO會引發 RecordsetChangeComplete 事件:

Private Sub rs_RecordsetChangeComplete( _
    ByVal adReason As ADODB.EventReasonEnum, _
    ByVal pError As ADODB.error, _
    adStatus As ADODB.EventStatusEnum, ByVal pRecordset As ADODB.Recordset)

此處的參數與 WillChangeRecordset 事件中同名參數的意義相同。有個未公開的事情您必須記住的是:對於Forward-Only Recordset,即使執行 MoveNext 方法也會引發 WillChangeRecordset 和 RecordsetChangeComplete 事件,這是因為其為無指標 Recordset;每次移到其他筆記錄時,ADO會重建Recordset物件。通常,對於任何不支援書籤的Recordset,當本地端的快取必須被重新填滿時(頻率依 CacheSize 屬性而定),皆會引發這些事件。

Field物件
 

Recordset物件有Fields集合,包含一或多個Field物件。每個Field物件代表資料來源的一資料行,其共有12個屬性和2個方法。

屬性
 

Field物件的屬性可被分成兩不同類:描寫性質和欄位特性(當Recordset被關閉也是有效的)的屬性和描寫在目前記錄中欄位內容的屬性(只有當Recordset被開啟和目前記錄有用的這些屬性才有效的)。

描述欄位特性
 

若附加Field物件到單獨存在的Recordset,則所有描寫Field物件特性的屬性(就是 metadata 屬性)都是可讀/寫的,而在Recordset開啟後就都成唯讀了。

Name 屬性是資料庫資料行的名稱,用以當成Field物件的讀與寫資料。因為此屬性也是在Field 集合中Field物件關連的索引鍵,可有三個方法中來找出特定欄位,使用下列的語法:

' Full syntax
rs.Fields("LastName").Value = "Smith"
' Fields is the Recordset's default property.
rs("LastName").Value = "Smith"  
' Value is the Field's default property.
rs("LastName") = "Smith"

通常使用 For...Next 或 For Each...Next 迴圈來得到Recordset內的欄位:

For i = 0 To rs.Fields.Count _ 1
    lstFieldNames.AddItem rs.Fields(i).Name
Next

Type 屬性傳回列舉的常數,此常數定義哪種值可以被儲存到欄位內。ADO支援的類型列在表13-5,但要知道不是所有OLE DB提供者和資料庫引擎都支援所有資料類型。Type 屬性也間接的影響 NumericScale、Precision 和 DefinedSize。


注意

在表13-5有些常數只適用Parameter物件(本章稍後會解釋)-至少,這是Visual Basic文件說得。然而,筆者發現這某些值也被使用在Field物件。例如,在MDB資料庫字串欄位的 Type 屬性傳回adVarChar的值。


常數 說明
adEmpty 0 無指定值
adSmallInt 2 2-byte具正負號整數
adInteger 3 4-byte具正負號整數
adSingle 4 單精準浮點數
adDouble 5 倍精準浮點數
adCurrency 6 金額
adDate 7 日期(格式與Visual Basic的Data變數相同)
adBSTR 8 以Null結尾的Unicode字串
adIDispatch 9 指向OLE物件Idispatch界面的指標
adError 10 32-bit錯誤碼
adBoolean 11 布林值
adVariant 12 Variant值
adIUnknown 13 指向OLE物件IUnknown界面的指標
adDecimal 14 固定準度與小數的數字
adTinyInt 16 1-byte具正負號整數
adUnsignedTinyInt 17 1-byte無正負號整數
adUnsignedSmallInt 18 2-byte無正負號整數
adUnsignedInt 19 4-byte無正負號整數
adBigInt 20 8-byte具正負號整數
adUnsignedBigInt 21 8-byte無正負號整數
adGUID 72 全球唯一識別碼(GUID)
adBinary 128 二進位值
adChar 129 字串
adWChar 130 以Null結尾的Unicode字元串
adNumeric 131 固定準度與小數的精確數值
adUserDefined 132 使用者自定變數
adDBDate 133 格式為「yyyymmdd」的資料值
adDBTime 134 格式為「hhmmss」
adDBTimeStamp 135 格式為「yyyymmddhhmmss」的日期或TimeStamp
adChapter 136 Chapter(在階層式Recordset的獨立Recordset)
adVarNumeric 139 變動長度,但有著固定準度與小數的精確數值
adVarChar 200 字串(只Parameter物件有)
adLongVarChar 201 Long變動長度字元串(只Parameter物件有)
adVarWChar 202 以Null結尾的Unicode字元串(只Parameter物件有)
adLongVarWChar 203 Long變動長度Unicode字元串(只Parameter物件有)
adVarBinary 204 二進位值(只Parameter物件有)
adLongVarBinary 205 Long變動長度、二進位資料(只Par ameter物件有)
表13-5 用在Field、Parameter和Property物件Type屬性的常數。

當欄位建立時,DefinedSize 屬性傳回定義的最大容量。NumericScale 屬性表示數字型態的小數(換句話,其表示小數點後的數值)。在數值Field物件中,Precision 屬性為數值的精準度(這是,阿拉伯數字的最大總數量表示此值)。Attributes 屬性為位元欄位值,其傳回關於欄位的訊息。可包含一個或多個列在表13-6的常數。

常數 說明
adFldMayDefer 2 延時欄位-亦即只有當欄位於程式中明確被引用時,才會擷取值的欄位
adFldUpdatable 4 欄位是可編修的
adFldUnknownUpdatable 8 Provider無法確定是否欄位可寫入
adFldFixed &H10 包含固定長度資料的欄位
adFldIsNullable &H20 接受Null值的欄位
adFldMayBeNull &H40 包含Null值的欄位(不需要接受之)
adFldLong &H80 為長二進位欄位(例如BLOB、CLOB),且可用AppendChunk與GetChunk
adFldRowID &H100 包含記錄指示的欄位,所謂記錄指示是不可寫入的,且除了指向列外無有意義值。
adFldRowVersion &H200 包含某些資料郵戳或時間郵戳(用來進行資料追蹤)的欄位
adFldCacheDeferred &H1000 當欄位第一次自資料庫讀入時會加以快取,而後續的讀取將會自快取區擷取
adFldKeyColumn &H8000 此欄位為索引鍵的一部份
表13-6 用在Filed物件 Attribute屬性的常數

描述欄位值
 

Value 屬性設定或傳回欄位的內容。其也是Field物件的預設屬性,所以可省略它。

rs.Fields("BirthDate") = #4/12/1955#

ActualSize 屬性是一個唯讀屬性,此屬性傳回欄位中目前值的位元數。不要將此屬性與 DefinedSize 屬性搞混,DefinedSize 屬性傳回欄位已宣告的最大長度。對於BLOB 和 CLOB欄位而言,這屬性特別有用。如果Provider不能確定欄位大小,則傳回-1。(Visual Basic文件說明此情況下此屬性傳回常數adUnknown;然而,此常數並未顯示在ADODB類型函式數中 。)

OriginalValue 屬性傳回在任何改變發生前的欄位值。如果在立刻更新狀態,這是 CancelUpdate 方法用來復原欄位內容的值。如果在批次更新狀態,這是在最後 UpdateBatch 方法後有效的值,也是 CancelBatch 方法用來復原欄位內容的值。

UnderlyingValue 屬性是目前儲存在資料庫的值。如果其他使用者在更新資料欄後,則此值將不同於 OriginalValue 屬性。Resync 方法會設定此值給 Value 屬性。一般來說,會將此屬性與 OriginalValue 屬性一同使用,以便解決從批次更新所引發的衝突。。

可設定 DataFormat 屬性為StdDataFormat物件以便可控制來從資料來源中的值如何被格式化。更多關於此的相關資訊,請參閱第八章 〈DataFormat屬性〉 節。

方法
 

Field物件只支援兩個方法,兩者皆用來處理大量二進元資料欄,如BLOB或CLOB欄位。(這些欄位的 Attributes 屬性的adFldLong位元皆有被設定)。因為這些欄位可以是好幾千個位元組長(或甚至好幾百萬個位元組),寫值進去,且將值以較小的分割讀回在實作上是較有幫助的。

AppendChunk 方法作用為寫入一「塊」(Chunk)資料到欄位中,其需要一個包含與寫入資料的Variant形態參數。通常,會以8KB或16KB的分割來寫入檔案內容,於多數情況下,會需要存放檔案內的一大塊資料,例如長文件或點陣圖。這有個可再使用的函式,其會將檔案的內容移到支援 AppendChunk 方法的欄位:

Sub FileToBlob(fld As ADODB.Field, FileName As String, _
    Optional ChunkSize As Long = 8192)
    Dim fnum As Integer, bytesLeft As Long, bytes As Long
    Dim tmp() As Byte
    ' Raise an error if the field doesn't support GetChunk.
    If (fld.Attributes And adFldLong) = 0 Then
        Err.Raise 1001, , "Field doesn't support the GetChunk method."
    End If
    ' Open the file; raise an error if the file doesn't exist.
    If Dir$(FileName) = " " Then Err.Raise 53, ,#"File not found"
    fnum = FreeFile
    Open FileName For Binary As fnum
    ' Read the file in chunks, and append data to the field.
    bytesLeft = LOF(fnum)
    Do While bytesLeft
        bytes = bytesLeft
        If bytes > ChunkSize Then bytes = ChunkSize
        ReDim tmp(1 To bytes) As Byte
        Get #1, , tmp
        fld.AppendChunk tmp
        bytesLeft = bytesLeft - bytes
    Loop
    Close #fnum
End Sub

第一次對給定的欄位呼叫此方法,其會覆蓋欄位目前的內容;於後每次的呼叫此方法會附加資料到欄位內。如果讀取或寫入記錄中其他欄位,然後並用 AppendChunk 方法重開始附加資料時,ADO會假定要附加全新的值並覆蓋欄位的內容。當針對副本Recordset的其他欄位時,ADO也會覆蓋欄位內容。但不是針對非現行其他Recordset副本的欄位時。

可使用 GetChunk 方法讀取儲存在包含一個長二進元值的欄位值。此方法需要一個參數:須從Field物件讀取的位元組數目。此方法的問題是如果讀太多位元組,ADO會將填塞傳回的字串為空白。如此空白通常是不想擷取的東西,特別當讀取影像或其他二進元資料時。由於這原因,應要測試 ActualSize 屬性來確定沒有讀入太多位元組。底下可重用之函式會自動做這項測試:

Sub BlobToFile(fld As ADODB.Field, FileName As String, _
    Optional ChunkSize As Long = 8192)
    Dim fnum As Integer, bytesLeft As Long, bytes As Long
    Dim tmp() As Byte
    ' Raise an error if the field doesn't support GetChunk.
    If (fld.Attributes And adFldLong) = 0 Then
        Err.Raise 1001, , "Field doesn't support the GetChunk method."
    End If
' Delete the file if it exists already, and then open a new one for writing.
    If Dir$(FileName) <> "" Then Kill FileName
    fnum = FreeFile
    Open FileName For Binary As fnum
    ' Read the field's contents, and write the data to the file 
    ' chunk by chunk.
    bytesLeft = fld.ActualSize
    Do While bytesLeft
        bytes = bytesLeft
        If bytes > ChunkSize Then bytes = ChunkSize
        tmp = fld.GetChunk(bytes)
        Put #fnum, , tmp
        BytesLeft = bytesLeft - bytes
    Loop
    Close #fnum
End Sub

說明

FileToBlob 和 BlobtoFile 函式在隨書光碟的函數庫裡。其他大部分函式皆在本章和 14章 。


多重 GetChunks 方法繼續來擷取資料自先前 GetChunk 方法遺留的那裡開始。但如果讀或寫相同Recordset的其他欄位值(或一個副本Recordset),下一次在原始的欄位中執行 GetChunk 方法時,ADO將從欄位開端重新開始。同樣,記住在SQL Server資料來源中,BLOB資料欄應為SELECT查詢的最後欄位。

Fields集合
 

可在兩個不同用途上使用Fields集合。最簡單和最直覺的方法是藉著反覆於其項目來擷取關於Recordset欄位的資訊-例如,當要建立一列欄位名稱和值時。

' Error trapping accounts for values, such as BLOB fields, that
' can't be converted to strings.
On Error Resume Next      
For i = 0 To rs.Fields.Count - 1
    LstFields.AddItem rs.Fields(i).Name & " = " & rs.Fields(i).Value
Next

Fields集合也支援 Append 方法,此方法建立一個新的Field物件並附加到集合內。當要在記憶體內產生Recordset物件而不需連結到資料來源(至少不是立刻地)時,此方法是很有用的。只可將此方法用在客戶端指標的Recordset(CursorLocation = adUseClient),且只在Recordset是關閉中與並未關連到Connection時(ActiveConnection = Nothing)才可使用。Append 方法的語法如下:

Append(Name, Type, [DefinedSize], [Attrib]) As Field

這些參數定義要被建立的Field物件的屬性。下列可重用的函式建立新的單獨存在Recordset,此Recordset有著與其他Recordset相同的欄位結構:

Function CopyFields(rs As ADODB.Recordset) As ADODB.Recordset
    Dim newRS As New ADODB.Recordset, fld As ADODB.Field
    For Each fld In rs.Fields
        newRS.Fields.Append fld.Name, fld.Type, fld.DefinedSize, _
            fld.Attributes
    Next
    Set CopyFields = newRS
End Function

底下為建立新單獨存在Record的函式,此Record不只複製一個已存在Recordset的欄位結構,還複製了其所包含的所有記錄(但非成為副本Recordset):

Function CopyRecordset(rs As ADODB.Recordset) As ADODB.Recordset
    Dim newRS As New ADODB.Recordset, fld As ADODB.Field
    Set newRS = CopyFields(rs)
    newRS.Open     ' You must open the Recordset before adding new records.
    rs.MoveFirst
    Do Until rs.EOF
        newRS.AddNew                        ' Add a new record.
        For Each fld In rs.Fields           ' Copy all fields' values.
            newRS(fld.Name) = fld.Value     ' Assumes no BLOB fields
        Next
        rs.MoveNext
    Loop 
    Set CopyRecordset = newRS
End Function

Fields集合也支援 Delete 方法,其會在開啟Fields集合前移除單獨存在記錄的欄位,也支援 Refresh 方法。


說明

似乎不能建立階層式單獨存在的Recordset。事實上,如果嘗試建立 Type 屬性為adChapter的Field,會產生錯誤。


Command物件
 

ADO Command物件定義命令或一個可在資料來源執行的查詢。當計畫執行相同命令或數次查詢(在相同或不同資料來源)且當進行預存函數或帶參數的查詢時,Command物件是有用的。回想這章較早部分提到,使用Connection 物件的 Execute 方法或Recordset物件的 Open 方法可以執行SQL查詢和命令。在實際的應用程式,會較喜歡使用Command物件做這項工作。當用SQL Server作業,例如,Command物件會自動地再使用第一次執行時SQL Server所建立的暫時預存函數,每次執行查詢均可傳遞不同的參數。

可建立單一且不和Connection物件相關的Command物件,然後藉由指定一個有效Connection物件給 ActiveConnection 屬性便可建立連結。藉由如此作業,可以再使用相同命令。

屬性
 

Command物件有9個屬性,但對於執行查詢或命令而言,只有兩個是必要的。

設定查詢
 

Command物件最重要的屬性是設定或傳回SQL命令或查詢、表格名稱、或預存函數名稱的 CommandText 。如果使用SQL查詢,其應在欲連結資料庫引擎內。根據指定給此屬性的字串與正使用的Provider的不同,ADO可能改變這屬性的內容。因為這原因,在指定值給 CommandText 後,還要再讀取以便確定將被用為查詢的真實值。底下為關於此的範例:

Dim cmd As New ADODB.Command
cmd.CommandText = "SELECT * FROM Employees WHERE BirthDate > #1/1/1960"

如果用不同參數重複查詢或命令,帶參數的Command物件是最方便的,在CommandText 屬性插入「?」符號:

Dim cmd As New ADODB.Command
cmd.CommandText = "SELECT * FROM Employees WHERE BirthDate > ? " _
    & "AND HireDate > ?"

CommandText 屬性告知Command物件做什麼,ActiveConnection 屬性決定命令應在哪個資料來源中執行。這屬性值可為連結字串(遵循Connection物件 ConnectionString 的語法),或已經指向資料來源的Connection物件。當設定這屬性為Nothing,表示切斷Command物件和釋放所有配置在伺服器的資源。如果在執行階段配置連結字串或Connection物件於這屬性前嘗試進行 Execute方法,會引發錯誤。如果指派已關閉的Connection物件給此屬性,也會發生錯誤。在轉換到其他連結之前,有些Provider會要求設定此屬性為Nothing。

只有若指定相同的Connection物件給其 ActiveConnection 屬性時,方可共享連結。若單單指定相同的連線字串則回建立不同的連結。底下為此能力的範例:

' Edit this constant to match your directory structure.
Const DBPATH = "C:\Program Files\Microsoft Visual Studio\Vb98\NWind.mdb"
' Create the first Command object.
Dim cmd As New ADODB.Command, rs As New ADODB.Recordset
cmd.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.3.51;" _
    & "Data Source= " & DBPATH
cmd.CommandText = "SELECT FirstName, LastName FROM Employees"
Set rs = cmd.Execute()
' Create a second Command object on the same database connection.
Dim cmd2 As New ADODB.Command, rs2 As New ADODB.Recordset
Set cmd2.ActiveConnection = cmd.ActiveConnection
cmd2.CommandText = "SELECT * FROM Customers"
Set rs2 = cmd2.Execute()

設定 ActiveConnection 屬性為Nothing會影響Parameters集合。更精確地說,如果Provider已經自動地設定Parameters集合,當 ActiveConnection 被設定為Nothing時,此集合將被清除。如果Parameters集合已經透過程式碼手動地設定,設定 ActiveConnection 為Nothing並沒影響。

最佳化執行
 

CommandType 屬性根據 CommandText 字串值來最佳化執行速度。其值可為以下常數:

說明
1-adCmdText SQL查詢
2-adCmdTable 資料表
4-adCmdStoredProc 預存函數
8-adCmdUnknown 由Provider決定類型(預設值)
512-adCmdTableDirect 直接開啟資料表

如果不指定此屬性值、或者使用adCmdUnknown,迫使ADO物件自行找出究竟 CommandText 字串為何,而這個動作通常會花費較多的負荷。adCmdStoredProc選項可以因避免ADO物件於執行查詢動作前建立一個暫時性的預存函數而增進效能。但若指定給 CommandType 屬性值與 CommandText 字串的類別不吻合的話,就會產生一個執行階段的錯誤。

Prepared性質可增進Command物件的效率,當此屬性為True時,Provider將會針對傳給 CommandText 屬性的查詢來建立一個編譯好的查詢,並且在重新執行查詢動作時使用此編譯好的查詢。建立一個編譯程序需耗費一段時間,所以建議你只在需要執行此查詢多次時,才把此屬性設為True。若資料來源不支援預先準備敘述,那就得看提供者物件會如何處理了:它可以發出一個錯誤,或者乾脆忽略此設定。

CommandTimeout 屬性設定或傳回命令執行後,任何錯誤發生前,ADO物件所會等待的秒數,預設值為30秒。若將此屬性設定為0秒,ADO物件將會永久地等待。此數值不會繼承自Connection物件的 CommandTimeout 屬性。也就是說,並非所有的Provider均支援此屬性。

State是個唯讀屬性,用於查詢何種命令是正在執行中。此屬性可回傳 0-adStateClosed(Command物件是在不活動狀態)或為4-adStateExecuting(Command物件正在執行命令)

方法
 

Command物件最重要的方法是 Execute,此方法會執行查詢動作或者CommandText 屬性中的命令。此方法和Connection物件的 Execute 方法是類似的,不過語法上有點不同,即是查詢字串不當參數來傳遞:

Execute([RecordsAffected], [Parameters], [Options]) As Recordset

若 CommandText 屬性包含傳回列的查詢,Execute方式會回傳一個開啟的Recordset(其實不見得一定要包含任何列)。反而言之,若屬性表示動作查詢,Execute 方式會回傳一個關閉的Recordset。在稍後的例子裡,可有效率地把Execute當成程序來取代函式,並可以忽略回傳值。

如果傳遞一個Long變數作為 RecordsAffected 參數,Execute 方法傳回被此動作查詢命令所影響的資料筆數。此參數是可選擇的,所以若沒傳遞任何參數,傳回列的查詢也不會傳回有意義的值。若執行的是參數化的命令或查詢,Parameters 將會是包含參數值或所有可預期參數陣列的變數:

' Edit this constant to match your directory structure.
Const DBPATH = "C:\Program Files\Microsoft Visual Studio\Vb98\NWind.mdb"
Dim cmd As New ADODB.Command, rs As New ADODB.Recordset
cmd.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.3.51;" _
    & "Data Source= " & DBPATH
cmd.CommandText = "SELECT * FROM Employees WHERE BirthDate > ? " _
    & "AND HireDate > ?"
cmd.CommandType = adCmdText
' You can pass mul小密訣le parameters without using a temporary array.
Set rs = cmd.Execute(, Array(#1/1/1960#, #1/1/1994#))

以這種方式傳遞的參數只有在現在正執行此命令時才會是有效的,而且也不會影響到Parameters集合。若在 Execute 方法中省略一或多個參數時,ADO物件會使用Parameters集合的相對應值。以下是個隱含使用Parameters集合第一個項目並只傳遞第二個參數的範例:

Set rs = cmd.Execute(, Array(, #1/1/1994#))

注意

雖然可以此方法傳遞任意數量的參數,但卻不能以此種方式接收輸出的參數。如果要執行包含輸出參數的預存函數,那只有透過參數集合才能接收這些數值。


Option 參數可為指定給 CommandType 屬性的相同數值,再加上下列常數其中之一:

說明
16-adAsyncExecute 非同步化執行命令
32-adAsyncFetch 非同步取得結果,得到的Recordset為客戶端指標
64-adAsyncFetchNonBlocking 語adAsyncFetch類似,但呼叫的程式不會停止執行
128-adExecuteNoRecords 指出形態為adCmdText或adCmdStoredProc的命令為動作查詢或不傳回Recordset

adAsyncExecute、adAsyncFetch和adAsyncFetchNonBlocking間有些細微的差異性。以上這些選項皆以非同步方式來執行命令,且在該命令回傳時會將 AffectedRecords設為-1,這是因為在命令回傳時,命令並未完全執行完畢,所以ADO物件根本不知道到底有多少筆的資料被影響。如果指定的是 adAsyncExecute,那麼命令將以非同步方式執行,且當Provider完成動作時,ADO物件會引發 ExecutionComplete 事件。如果對於使用者端游標的資料集指定adAsyncFetch的話,當指令執行完畢後,ADO物件將會以非同步的方式來取回資料列:當程式要求尚未接收到的資料列時,應用程式會卡在此處,一直等到資料是可使用時(這時即會發出一個 FetchComplete 事件)。而adAsyncFetchNonBlocking和adAsyncFetch其實是非常相似的,其中最重要的區別是:當程式要求尚未接收到的資料列時,應用程式將不會卡在此處,並且會把Recordset的EOF屬性設為True,因此此指令稍後會再重試,或等待 FetchComplete 事件來決定何時資料是可使用的。可利用Command物件的 Cancel 方法來取消非同步的執行。不過此方法會在最近的 Execute 方法沒有指定任何非同步選項時引起錯誤。

Command物件所支援的第三種方法是 CreateParameter。使用這種方法時,可在程式中建立Parameters集合,而不必往來於伺服器之間。此種方式的語法如下:

CreateParameter([Name], [Type], [Direction], [Size], [Value]) As Parameter

每個傳遞至此方法的參數,均會被指定給被建立的Paramete物件的屬性。下一節將對於這些屬性再作詳細的介紹。

Parameter物件
 

Parameter物件以參數化的命令或Command物件的預存函數來描述參數。理論上來說,Provider是有可能不支援參數化命令,雖然大多數的Provider都會支援此方式。Parameter物件可以描述一個命令的輸入值或是個查詢動作或是個輸出值或者是個預存函數的回傳值。所有相關於Command物件的Parameter物件都包含在Command的Parameters集合。

ADO 物件在處理Parameters集合上表現不錯。當參考到一個Command物件的Parameters 屬性時,其會自動建立集合。不過ADO物件也給予一些空間允許於程式中自行建立集合,而這功能在DAO或RDO物件中並未法提供。通常自行建立這個Parameters集合會獲得較佳的效率,因為節省ADO物件往來伺服器間來決定所有參數的名稱和類別。換句話說,如果想要ADO物件接收所有參數的名稱和類別,唯一要做的是對Command物件的Parameters集合下達 Refresh 的指令,如下例:

Cmd.Parameters.Refresh

呼叫 Refresh 指令是可選擇的,因為若在尚未建立Parameters集合內元素時就存取此集合的話,ADO物件會自動更新此集合。

屬性
 

Parameter物件包含九種屬性。這些屬性絕大多數和Field物件所涵蓋的屬性相似。因為這些相似之處,於此就不再對於這些屬性做更進一步的描述。譬如,每個Parameter物件的 Name、Type、Precision 和 NumericScale 屬性,Field物件也含有這些屬性。表13-5 列出所有 Type 屬性可能的值。(不過請注意,Parameter物件支援某些Field物件不支援的類別)。

Parameter物件也支援 Value 屬性。這是此物件的預設屬性,所以可省略之:

cmd.Parameters("StartHireDate") = #1/1/1994#

Direction 屬性指定哪個Parameter物件代表輸入參數,輸出參數,或預存函數的回傳值。下列為幾個列舉常數表示:

說明
0-adParamUnknown 不知名的方向
1-adParamInput 輸入參數(預設值)
2-adParamOutput 輸出參數
3-adParamInputOutput 輸出入參數
4-adParamReturnValue 預存函數的傳回值

此屬性均可以讀取或寫入,尤其在Provider無法決定預存函數中參數的方向時是非常有用的。

ttributes 屬性表示Parameter物件的部份特性。此為位元欄位值,可以為下列值的和:

說明
16-adParamSigned 此參數接受符號值
64-adParamNullable 此參數接受Null值
128-adParamLong 此參數接受長二進位資料

Size 屬性設定與傳回Parameter物件值的最大長度。若想建立變動長度的資料類別的Parameter物件(如:字串類別),就必須在附加此參數至Parameters集合前設定此屬性;否則會產生錯誤。如果已將Parameter物件附加至Parameters集合,且稍後要將類別更改為變動長度的資料類別,那麼必須在執行 Execute 方式前設定 Size 屬性。

Size 屬性在使Provider自動建立Parameters集合時也是很有用的。當集合包含一或多個的變動項目時,ADO物件會基於最大的可能範圍內要求給予這些參數的記憶體空間,雖然這可能會導致以後產生錯誤。而可在執行此命令前經由確實設定正確的數值給予 Size 屬性來避免這樣的錯誤。

方法
 

AppendChunk 方法是Parameter物件唯一支援的方式。此方式和Field物件內的AppendChunk功能是相同的,在此不再贅述。你可以測試Parameter物件 Attributes 屬性中的 adParamLong 位元來得知是否支援此方式。

Parameters集合
 

每個Command物件包含一個 Parameters 屬性,而此屬性回傳Parameter集合的參考。在本章稍早曾提及:可讓ADO物件自動建立此集合,或經由建立Parameter物件與以手動方式將物件加入至集合中來節省某些 ADO 物件的工作。可用Command物件的 CreateParameter 方法連接Parameters集合的 Append 方法,如以下的範例:

' Edit this constant to match your directory structure.
Const DBPATH = "C:\Program Files\Microsoft Visual Studio\Vb98\NWind.mdb"
Dim cmd As New ADODB.Command, rs As New ADODB.Recordset
cmd.CommandText = "Select * From Employees Where BirthDate > ? " _
    & "AND HireDate > ?"
cmd.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.3.51;" _
    & "Data Source= " & DBPATH
' You can use a temporary Parameter variable.
Dim param As ADODB.Parameter
Set param = cmd.CreateParameter("BirthDate", adDate, , , #1/1/1960#)
cmd.Parameters.Append param
' Or you can do everything in one operation. 
cmd.Parameters.Append cmd.CreateParameter("HireDate", adDate, , , _
    #1/1/1993#)
Set rs = cmd.Execute(, , adCmdText)

當需要執行某項動作多次時,參數化的查詢和命令是非常有用的,因為對於後續的動作,只需修改參數值即可。

' You can reference a parameter by its index in the collection.
cmd.Parameters(0) = #1/1/1920#
' But you deliver more readable code if you reference it by its name.
cmd.Parameters("HireDate") = #1/1/1920#
Set rs = cmd.Execute()

可用Parameters集合中的 Delete 方式來刪除一個項目,另外也可用 Count屬性來得知集合包含多少個元素。當Command物件指向一個具有回傳值的預存函數時,Parameters(0)即代表回傳值。

Property物件
 

Connection、Recordset、Command與Field物件皆包含Properties集合,其包含所有ADO Provider內建的動態屬性。無法自行增加動態屬性;因此Properties集合只包含 Count 和 Item 屬性以及 Refresh 方法。

在進階 ADO 程式設計中,動態屬性是相當重要的一環,因為其常提供關於ADO 物件的支援資訊。有時甚至可藉由指定不同的值給這些動態屬性來改變Provider的行為。即使OLE DB規格中列出少數縱使跨Provider時也應要有相同意義的屬性,每個Provider還是包含不同的動態屬性。這有一段程式,其用某傳入物件的所有動態屬性值來填滿ListBox控制項:

Sub ListCustomProperties(obj As Object, lst As ListBox)
    Dim i As Integer, tmp As String
    On Error Resume Next
    lst.Clear
    For i = 0 To obj.Properties.Count - 1
        lst.AddItem obj.Properties(i).Name & " = " & obj.Properties(i)
    Next
End Sub

這Properties集合包含一個以上的Property物件,其包含四個屬性:Name、Value、Type 與 Attributes。Type 屬性可是個從列在表13-5中選出的列舉值。Attributes 屬性是一個bit-欄位值,可為下列常數的幾項和:

說明
1-adPropRequired 在資料來源初始化前,使用者必須指定值給此屬性
2-adPropOptional 在資料來源初始化前,使用者不需要指定值給此屬性
512-adPropRead 使用者可讀入此屬性值
1024-adPropWrite 使用者可指定值給此屬性

若 Attributes 屬性傳回值0-adPropNotSupported,則表示提供者沒有支援此屬性。

ADO 2.1 DDL和Security Extensions
 

ADO的美妙之處在於其具有可延伸的架構。它不是單一(且非常複雜)的物件模型,不過DAO是這樣的。Microsoft可以很容易地增加新的特性給ADO,而不會破壞已存在的應用程式,也不會強迫開發者於每次版本更新時,得學習一個新的物件模型。事實上,雖然ADO 2.1對ADO 2.0模型而有著些改善,不過所有新的特性皆由獨立的物件樹動態地來連結到-亦即於執行階段-標準ADO物件模型的物件上。

在這章的剩餘部分,筆者將說明Microsoft Extension 2.1對DDL 和 Security (ADOX)函式庫的物件,其延伸了標準ADODB函式庫,使其支援資料定義語法,例如於資料庫中定義資料表、檢視與預存程序(如同建立新的一樣)的能力。這函式庫也包含安全物件,允許決定和修改對一個(或一群)使用者的許可權。ADO 2.1還包含其他延伸,例如針對線上分析程序(OLAP)的ADOMD函式庫,和Microsoft Jet replications的支援,不過本書並未涵蓋這些主題。

圖13-5為 ADOX物件圖。這物件樹包括比ADODB還要多的項目,不過其關係仍是非常直覺的。標準ADO函式庫著墨在資料庫內的資料,而ADOX函式庫則只對資料庫內的資料表、檢視和程序等結構及可存取這些項目的使用者感興趣。運用ADOX函式庫一點兒也不難,因為您無須考慮Recordset、cursor、逾時錯誤、鎖定、交易行為和當撰寫架構在ADO的標準資料庫程式時,所有必須解決的事情。所有在階級組織的物件也支援Properties集合,其包括所有動態屬性。


 

圖13-5 ADOX物件模型。Catalog物件可以透過Catalog的ActiveConnection屬性連結到一個存在的ADODB.Connection物件。

注意

並非所有提供者皆支援本章所說明的DDL能力。例如,一提供者可能支援資料庫物件的列舉但沒有支援建立。基於此原因,確定所有使用此物件的程式皆有能力防範不可預期的錯誤是必須的。


Catalog物件
 

Catalog物件是ADOX階級組織的入口。它代表資料庫且包括所有資料表、預存程序、檢視、使用者和使用者群組。Catalog物件有兩個不同的運作:列舉已存在資料庫內的物件或從頭建立新的資料庫。

當只要去瀏覽已存在資料庫時,必須要建立一個單獨存在(stand-alone)的ADODB.Connection物件,開啟它,然後將它指定給Catalog物件的 ActiveConnection 屬性。下列程式說明如何將ADODB 和 ADOX階級組織連結在一起:

' Edit this constant to match your directory structure.
Const DBPATH = "C:\Program Files\Microsoft Visual Studio\Vb98\Biblio.mdb"
Dim cn As New ADODB.Connection, cat As New ADOX.Catalog
' Open the connection.
cn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & DBPATH 
' Link the catalog to the connection.
Set cat.ActiveConnection = cn

在把Catalog物件連結到一個開啟的Connection物件後,便可列舉資料庫內的物件,只需藉著Tables、Procedures、Views、Groups和Users集合:

' Fill a list box with the names of the stored procedures in the database.
Dim proc As ADOX.Procedure
For Each proc In cat.Procedures
    List1.AddItem proc.Name
Next

在隨書光碟有個完整的專案,其列出Catalogk的所有物件與其所有屬性值,如圖13-6所示。

Catalog物件包含兩個方法,GetObjectOwner 和 SetObjectOwner,其作用為讀出和修改資料庫物件的擁有者,如下列例子:

On Error Resume Next      ' Not all providers support this capability.
owner = cat.GetObjectOwner("Authors", adPermObjTable)

當要建立一個新的(空的)資料庫時,並不需要一個stand-alone Connection物件。

藉著Catalog物件的 Create 方法便可達成。此方法只需要一個參數,就是定義提供者與資料庫名稱的連線字串。

' The next line fails if the database already exists.
cat.Create "Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;" _
    & "Data Source=C:\Microsoft Visual Studio\Vb98\BiblioCopy.mdb"

Create 方法並未被SQL Server、Orcale與ODBC drivers的OLE DB Provider所支援。

不論是建立一個新資料庫或開啟一個存在資料庫,都可用Catalogs集合來增加或移除物件。例如,這有一段程式碼,其建立有著兩個欄位的新資料表,並將它加到資料庫內:

Dim tbl As New ADOX.Table
tbl.Name = "Customers"                     ' Create a table.
tbl.Columns.Append "CustID", adInteger     ' Add two fields.
tbl.Columns.Append "Name", adWVarChar, 50
cat.Tables.Append tbl                      ' Append the table to
                                           ' the collection.


 

圖13-6 ADOX應用程式顯示Catalog內的所有物件和其關係。

Tabel物件
 

Table物件是ADOX階層組織中最複雜的物件。其包含四個簡單的屬性-Name、Type、DateCreated 與 DateModified-還有Columns、Indexes、Keys與Properties集合。由於其名稱已很明顯地說明其用途,所以筆者將不再詳細說明。Table物件未包含任何方法。

資料庫內的所有資料表皆包含在Table集合裡。此集合包含常見的 Item 和 Count 屬性與 Append、Delete 和 Refresh 方法。例如,藉著使用下列程式碼可列舉資料庫的所有資料表與每個資料表的所有行:

Dim tbl As ADOX.Table, col As ADOX.Column
For Each tbl in cat.Tables
    Print "TABLE " & tbl.Name
    Print "Created on " & tbl.DateCreated
    Print "Modified on " & tbl.DateModified
    Print "Field List ------"
    For Each col In tbl.Columns
        Print "   " & col.Name
    Next
Next

不能使用Orcale與ODBC drivers的OLE DB Provider來增減資料表。

Column物件
 

Column物件和相對的Columns集合在ADOX階級組織許多地方出現,也是Table、 Index和Key物件的相依物件。Column物件包含一些屬性,不過並非在任何情況下全部都可用。當Column物件相依於Table物件時,Name、Type、DefinedSize、NumericScale 和 Precision 屬性可讀入值,其與ADODB.Field物件的同名屬性之意義相同。Column物件也支援 Attributes(bit-field)屬性,其值可以是1-adColFixed 或 2-adColNullable。

若Column物件相依於Key物件,也可設定或傳回 RelatedColumn 屬性值,其定義相關資料表的相關欄位名。若Column物件相依於Index物件,則可設定 SortOrder 屬性值為1-adSortAscending或2-adSortDescending。

可藉由個別Columns集合的 Append 方法來把Column物件加到資料表、索引、索引鍵內。此方法需要Column名稱、形態、(可選擇的)要被建立的Column物件的 DefinedSize 屬性值作為參數:

' Add two fields to the Customers table.
Dim tbl As ADOX.Table
Set tbl = cat.Tables("Customers")
tbl.Columns.Append "CustID", adInteger
tbl.Columns.Append "Name", adVarChar, 255

Index物件
 

可透過Indexes集合列舉資料表的索引。Index物件包含少數屬性,其名稱就很明顯說明其意:Name、Clustered(如果索引是叢集的話,值為True)、Unique(如果索引是唯一的話,值為True)和 PrimaryKey(如果索引對資料表而言是主鍵的話,值為True)。唯一需要加以說明的屬性是 IndexNulls,其決定是否值為Null的記錄可在索引中出現。此屬性值可以是下列值之一:

說明
0-adIndexNullsAllow 可接受Null值
1-adIndexNullsDisallow 若鍵值欄位有Null值,建立索引時會發生錯誤
2-adIndexNullsIgnore 有Null值的欄位未被忽略,且不會加到索引內
4-adIndexNullsIgnoreAny 在多重索引中,若任一索引欄位有Null值,則該記錄不會被加入索引

要增加索引到資料表中,得建立一個stand-alone Index物件,設定其屬性如所需,加一個以上的項目到其Columns集合,最後將此索引加到Table物件的Indexes集合中。

Dim tbl As ADOX.Table, ndx As New ADOX.Index
' Create a new index.
ndx.Name = "YearBorn_Author"
ndx.Unique = True
' Append two columns to it.
ndx.Columns.Append "Year Born"
ndx.Columns("Year Born").SortOrder = adSortDescending
ndx.Columns.Append "Author"
' Add the index to the Authors table.
Set tbl = cat.Tables("Authors")
tbl.Indexes.Append ndx

只能在索引被附加到Table物件的Indexes集合前修改Index物件的所有屬性。當增加一個欄位到Index物件的Columns集合時,如果資料行並不存在於Table物件或如果Table物件還沒加到Catalog物件的Tables集合時,會發生錯誤。

Key物件
 

Key物件表示資料表的鍵值欄位。可藉由Table物件的Keys集合來確定其鍵值欄位,或可用集合的 Append 方法來增加新鍵值。當鍵值尚未加到集合內時,可設定其 Name 與 Type 屬性。Type 屬性定義鍵值的類型,其值可以是:1-adKeyPrimary(主鍵)、2-adKeyForeign(外鍵)或3-adKeyUnique(唯一鍵)。

如果鍵值是外鍵的話,則有三個屬性可使用。RelatedTable 屬性包含關連資料表的名稱,UpdateRule 和 DeleteRule 屬性決定若在關連資料表的紀錄被更新或刪除時,會有什麼反應。 UpdateRule 和 DeleteRule 屬性值可以是:

說明
0-adRINone 什麼事都不發生
1-adRICascade 改變會連串下去
2-adRISetNull 鍵值設為Null
3-adRISetDefault 鍵值被設成其預設值

每個Key物件包含一個Columns集合,其涵蓋建立此鍵的所有資料行。下列程式說明如何增加一個新鍵到資料表內:

' Add a foreign key to the Orders table, and make the key point
' to the EmployeeID field of the Employees table.
Dim tbl As ADOX.Table, key As New ADOX.Key
Set tbl = cat.Tables("Orders")
' Create the key, and set its attributes.
key.Name = "Employee"
key.Type = adKeyForeign
key.RelatedTable = "Employees"
key.UpdateRule = adRICascade
' Add a column to the key, and set its RelatedColumn attribute.
key.Columns.Append tbl.Columns("EmployeeId")
key.Columns("EmployeeId").RelatedColumn = "EmployeeId"
' Append the key to the table's Keys collection.
tbl.Keys.Append key

View與Procedure物件
 

View物件和Procedure物件是相似的。它們分別表示資料庫的檢視與預存函式。它們也包含相同的四個屬性:Name、DateCreated、DateModified 和 Command。Command 屬性提供給這些物件最大的彈性,而不會使得ADOX階層組織比所要求的還複雜。事實上,Command 屬性傳回一個可執行檢視或預存程序的ADODB.Command引用,所以可以確定舊SQL命令的名稱與任何參數的形態,就像使用此Command物件的屬性般。下列程式碼示範如何獲得這些資訊:

Dim cmd As ADODB.Command
Set cmd = cat.Views("All Titles").Command
MsgBox cmd.CommandText

當要建立一個新檢視或預存程序時,也可使用此輔助Command物件,如下列程式片段:

' Edit this constant to match your directory structure.
Const DBPATH = "C:\Program Files\Microsoft Visual Studio\Vb98\Biblio.mdb"
Dim cn As New ADODB.Connection, cmd As New ADODB.Command
' 注意事項 the version number of the Jet OLE DB Provider.
cn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & DBPATH
Set cmd.ActiveConnection = cn 
cmd.CommandText = "Select * From Authors Where [Year Born] = [Year]"
cmd.Parameters.Append cmd.CreateParameter("Year", adInteger, adParamInput)
' Open the Catalog, and create the new procedure. 
Set cat.ActiveConnection = cn
Cat.Procedures.Append "AuthorsByYear", cmd

Views不被SQL Server的OLE DB Provider所支援。ODBC和Oracle的提供者則支援它們,但只可以列舉它們-不能增加或刪除個別View物件。沒有任何提供者

可建立或刪除Procedure物件。

Group與User物件
 

Catalog物件包含Groups和Users集合,其包括一群使用者和個別使用者,其可存取一些或所有的資料庫物件。這兩個物件關係密切,因為每個User物件包含一個Groups集合(表示使用者屬於的所有的群組),而每個Group物件包含一個Users集合(表示所有屬於此群組的使用者)。

可使用 GetPermissions 方法得到User物件或Group物件的許可權。因為此方法傳回bit-欄位值,所以必須使用AND布林運算方能得知哪個權限是被允許的:

' Displays which permissions on the Customers table
' have been granted to the users in the Guests group.
Dim grp As ADOX.Group, permissions As Long
Set grp = cat.Groups("Guests")
permissions = grp.GetPermissions("Customers", adPermObjTable)
If permissions And adRightExecute Then Print "Execute"
If permissions And adRightRead Then Print "Read"
If permissions And adRightUpdate Then Print "Update"
If permissions And adRightInsert Then Print "Insert"
If permissions And adRightDelete Then Print "Delete"
If permissions And adRightReference Then Print "Reference"
If permissions And adRightCreate Then Print "Create"
If permissions And adRightWriteDesign Then Print "Design"
If permissions And adRightWithGrant Then Print "Grant Permissions"

SetPermission 方法可針對一給定的資料庫物件設定、獲得、拒絕或撤回對User或Group的許可權。

' Revoke the Guests group the permission to read the Customers table.
cat.Users("Guests").SetPermissions "Customers", adPermObjTable, _
    adAccessRevoke, adRightRead
' Give the Managers group full permissions on the Employees table.
cat.Users("Managers").SetPermissions "Employees", adPermObjTable, _
    adAccessSet, adRightFull

Groups和Users集合不被SQL SEVER、Oracle和ODBC Driver的OLE DB Provider所支援。

在本章,筆者說明了ADO階層的所有物件和它們的屬性、方法和事件。儘管筆者已經向你說明如何使用ADO完成複雜的工作,但仍然未實作說明如何善用ADO最先進的特性來建立完整的資料庫程式。第14章將專注在如何使用這些物件。