VFP的數據策略:高級篇
作者:Doug Hennig 翻譯:老瓷
引語
在“VFP中的數據策略:基礎篇”一文中,我們研究了VFP應用程序中訪問非VFP數據(如SQL Server)的不同機制:遠程視圖、SQL Passthrough、ADO、XML和VFP 8中添加的CursorAdapter類。在本文中,我們將更詳細地討論CursorAdapter,並討論可重用數據類的概念。此外,我們將簡要介紹新的XMLAdapter基類,並了解它如何幫助與其他源(如ADO.NET)交換數據。
CursorAdapter
在我看來,CursorAdapter是VFP 8中最大的新特性之一。我覺得他們這么酷的原因是:
- 使得使用ODBC、ADO或XML變得容易,即使您不太熟悉這些技術。
- 為遠程數據提供了一致的接口,而不管您選擇何種機制。
- 使從一種機制切換到另一種機制變得容易。
最后是一個例子。假設您有一個應用程序使用帶有CursorAdapter的ODBC來訪問SQL Server數據,出於某種原因,您希望更改為使用ADO。您只需更改CursorAdapters的DataSourceType並更改到后端數據庫的連接,就完成了。應用程序中的其他組件既不知道也不關心這一點;它們仍然看到同一個游標,而不管用於訪問數據的機制如何。
讓我們開始通過查看CursorAdapter的屬性、事件和方法(PEMs)來檢查它們。
PEMS
這里我們不討論CursorAdapter類的所有屬性、事件和方法,只討論更重要些的屬性、事件和方法。有關完整列表,請參閱VFP文檔。
(PEMS:屬性、事件、方法統稱的縮寫——譯者注)
DataSourceType
這個屬性很重要:它決定了類的行為,以及將什么類型的值放入其他一些屬性中。有效的選項是“Native”,這表示您使用的是Native表,或者是選擇“ODBC”、“ADO”或“XML”,這表示您使用了適當的機制來訪問數據。您可能不會使用“Native”,因為您可能會使用Cursor對象而不是CursorAdapter,但此設置將使以后升遷應用程序更容易。
DataSource
這是訪問數據的方法。當DataSourceType設置為“Native”或“XML”時,VFP忽略此屬性。對於ODBC,將DataSource設置為有效的ODBC連接句柄(這意味着您必須自己管理連接)。對於ADO,數據源必須是一個ADO記錄集,該記錄集的ActiveConnection對象設置為打開的ADO連接對象(同樣,您必須自己管理)。
UseDEDataSource
如果此屬性設置為.T.(默認值為.F.),則可以不使用DataSourceType和DataSource屬性,因為CursorAdapter將使用數據環境(DataEnvironment)的屬性(VFP 8也將DataSourceType和DataSource添加到DataEnvironment類)。將此設置為.T.的一個示例是,希望數據環境中的所有CursorAdapter使用相同的ODBC連接。
SelectCmd
對於除了XML以外的所有內容,這是用於檢索數據的SQL SELECT命令。對於XML,這可以是可以轉換為游標的有效XML字符串(使用內部XMLToCursor()調用)或返回有效XML字符串的表達式(如UDF)。
CursorSchema
此屬性保存游標的結構,其格式與您在CREATE Cursor命令中使用的格式相同(此類命令中括號之間的所有內容)。這里有一個例子:CUST_ID C(6),COMPANY C(30),CONTACT C(30),CITY C(25)。盡管可以將此項留空,並告訴CursorAdapter在創建游標時確定結構,但如果將CursorSchema填充進來,效果會更好。首先,如果CursorSchema為空或不正確,則在打開窗體的數據環境時可能會出錯,或者無法將字段從CursorAdapter拖放到窗體以創建控件。幸運的是,VFP附帶的CursorAdapter構建器可以自動為您填充這個內容。
AllowDelete, AllowInsert, AllowUpdate, and SendUpdates
這些屬性(默認為.T.)決定是否可以執行刪除、插入和更新,以及是否將更改發送到數據源。
KeyFieldList, Tables, UpdatableFieldList, and UpdateNameList
如果希望VFP使用游標中所做的更改自動更新數據源,則需要這些屬性,這些屬性的用途與視圖的同名CursorSetProp()屬性相同。KeyFieldList是一個逗號分隔的字段列表(不帶別名),這些字段構成游標的主鍵。表是一個逗號分隔的表列表。UpdateableFieldList是一個逗號分隔的字段列表(沒有別名),可以更新。UpdateNameList是一個逗號分隔的列表,它將游標中的字段名與表中的字段名相匹配。UpdateNameList的格式如下:CursorFieldName1 Table.FieldName1,CursorFieldName2 Table.FieldName2……請注意,即使UpdateableFieldList不包含表的主鍵的名稱(因為您不希望更新該字段),它也必須仍然存在於UpdateNameList中,否則更新將不起作用。
*Cmd, *CmdDataSource, *CmdDataSourceType
如果要特別控制VFP如何刪除、插入或更新數據源中的記錄,可以為這些屬性集指定適當的值(將上面的*替換為Delete、Insert和Update)。
CursorFill(UseCursorSchema, NoData, Options, Source)
此方法創建游標並用數據源中的數據填充它(盡管可以通過.T.使NoData參數創建空游標)。對於第一個使用CursorSchema或.F.中定義的模式的參數,傳遞.T.。以從數據源創建適當的結構(在我看來,這種行為是相反的)。必須設置多鎖,否則此方法將失敗。如果CursorFill由於任何原因失敗,它將返回.F.,而不是引發錯誤;使用AERROR()來確定出了什么問題(盡管准備好進行一些深挖,因為您經常收到的錯誤消息不夠具體,無法確切地告訴您問題是什么)。
CursorRefresh()
此方法類似於ReQuery()函數:它刷新游標的內容。
Before*() and After*()
幾乎每個方法和事件都有前后“鈎子”事件,允許您自定義CursorAdapter的行為。例如,在AfterCursorFill中,可以為游標創建索引,使其始終可用。對於Before事件,可以返回.F.以防止觸發它的操作發生(這與數據庫事件類似)。
下面是一個示例(CursorAdapterExample.prg),它從SQL Server附帶的Northwind數據庫的Customers表中獲取巴西客戶的某些字段。游標是可更新的,因此如果您在游標中進行了更改,請將其關閉,然后再次運行程序,您將看到您的更改已保存到后端。
local loCursor as CursorAdapter, ; laErrors[1] loCursor = createobject('CursorAdapter') with loCursor .Alias = 'Customers' .DataSourceType = 'ODBC' .DataSource = sqlstringconnect('driver=SQL Server;' + ; 'server=(local);database=Northwind;uid=sa;pwd=;trusted_connection=no') .SelectCmd = "select CUSTOMERID, COMPANYNAME, CONTACTNAME " + ; "from CUSTOMERS where COUNTRY = 'Brazil'" .KeyFieldList = 'CUSTOMERID' .Tables = 'CUSTOMERS' .UpdatableFieldList = 'CUSTOMERID, COMPANYNAME, CONTACTNAME' .UpdateNameList = 'CUSTOMERID CUSTOMERS.CUSTOMERID, ' + ; 'COMPANYNAME CUSTOMERS.COMPANYNAME, CONTACTNAME CUSTOMERS.CONTACTNAME' if .CursorFill() browse else aerror(laErrors) messagebox(laErrors[2]) endif .CursorFill() endwith
數據環境和表單更改
為了支持新的CursorAdapter類,對DataEnvironment、Form類及其設計器進行了一些更改。
首先,如前所述,DataEnvironment類現在有DataSource和DataSourceType屬性。它本身不使用這些屬性,但已將UseDataSource設置為.T.的任何CursorAdapter成員都使用這些屬性。其次,現在可以使用類設計器(woo-hoo!)可視化地創建DataEnvironment子類。
至於表單,現在可以通過設置新的DEClass和DEClassLibrary屬性來指定要使用的DataEnvironment子類。如果您這樣做,您對現有數據環境所做的任何事情(游標、代碼等)都將丟失,但至少您會首先收到警告。表單的一個很酷的新特性是BindControls屬性;在屬性窗口中將其設置為.F. 意味着VFP不會在初始化時嘗試對控件進行數據綁定,只有在將BindControls設置為.T.時才會這樣做。這有什么好處?好吧,您詛咒參數傳遞給Init多少次了,Init在所有控件初始化並綁定到它們的ControlSource之后觸發?如果要將參數傳遞給告訴它要打開哪個表的窗體或其他影響ControlSources的內容,該怎么辦?這個新屬性使這個問題很快解決。
其他變化
CursorGetProp('SourceType')返回一個新的值范圍:如果游標是用CursorFill創建的,則該值為100加上舊值(例如,遠程數據為102)。如果游標是用CursorAttach創建的(允許您將現有游標附加到CursorAdapter對象),則該值為200加上舊值。如果數據源是ADO記錄集,則值為104(CursorFill)或204(CursorAttach)。
生成器
VFP包括DataEnvironment和CursorAdapter構造器(或稱為生成器——譯者注),使得使用這些類更加容易。
以正常方式啟動DataEnvironment Builder:在類設計器中右鍵單擊窗體的DataEnvironment或DataEnvironment子類,然后選擇Builder。數據環境生成器的“數據源”頁是設置數據源信息的位置。選擇所需的數據源類型和數據源的來源。如果選擇“使用現有連接句柄”(ODBC)或“使用現有ADO記錄集”(ADO),請指定包含數據源的表達式(例如“goConnectionMgr.nHandle”)。您還可以選擇使用系統上的任一個DSN或連接字符串。只有在為ADO選擇“使用連接字符串”時才會啟用“生成”按鈕,該按鈕將顯示“數據鏈接屬性”對話框,您可以使用該對話框直觀地生成連接字符串。如果選擇“使用DSN”或“使用連接字符串”,生成器將在數據環境的BeforeOpenTables方法中生成代碼以創建所需的連接。如果選擇“Native”,則可以選擇VFP數據庫容器作為數據源;在這種情況下,生成的代碼將確保數據庫是打開的(也可以使用自由表作為數據源)。
“Cursors”頁面允許您維護DataEnvironment的CursorAdapter成員(游標對象不會在生成器中顯示,也不能添加它們)。Add按鈕允許您向DataEnvironment添加CursorAdapter子類,而New則創建一個新的基類CursorAdapter。Remove刪除Select CursorAdapter,Builder為所選CursorAdapter調用CursorAdapter Builder。您可以更改CursorAdapter對象的名稱,但對於任何其他屬性,都需要CursorAdapter生成器。
從快捷菜單中選擇Builder也可以調用CursorAdapter生成器。“Properties”頁顯示對象的類和名稱(只有在從DataEnvironment中調出生成器時才能更改名稱,因為它對CursorAdapter子類是只讀的)、它將創建的游標的別名、是否應該使用DataEnvironment的數據源以及連接信息(如果沒有)。與DataEnvironment生成器一樣,如果選擇“使用DSN”或“使用連接字符串”,CursorAdapter生成器將生成代碼以創建所需的連接(在本例中是CursorFill方法)。
“數據訪問”頁允許您指定SelectCmd、CursorSchema和其他屬性。如果您指定了連接信息,可以單擊SelectCmd的Build按鈕來顯示Select Command Builder,這樣就可以輕松地創建SelectCmd。
Select命令生成器簡化了構建一個簡單的Select語句的工作。從“表格”下拉列表中選擇所需的表格,然后將相應的字段移到選定的一側。對於本機數據源,可以向“表”組合框中添加表(例如,如果希望使用空閑表)。選擇OK時,SelectCmd將填充適當的SQL SELECT語句。
單擊游標模式的“生成”按鈕,自動為您填寫此屬性。為了使其工作,生成器實際上創建了一個新的CursorAdapter對象,適當地設置了屬性,並調用CursorFill來創建游標。如果您沒有到數據源的實時連接,或者CursorFill由於某種原因(例如無效的SelectCmd)失敗,那么這顯然行不通。
使用“自動更新”頁設置VFP自動為數據源生成更新語句所需的屬性。Tables屬性是從SelectCmd中指定的表自動填充的,fields網格是從CursorSchema中的字段填充的。與視圖設計器一樣,可以通過檢查網格中的相應列來選擇哪些是關鍵字段,哪些字段是可更新的。還可以設置其他屬性,例如在將游標發送到數據源之前轉換游標某些字段中的數據的函數。
更新、插入和刪除頁面的外觀幾乎相同。它們允許您為更新、刪除和插入屬性集指定值。對於VFP不能自動生成update語句的XML,這一點尤為重要。
使用本機數據
盡管很明顯CursorAdapter的目的是為了標准化和簡化對非VFP數據的訪問,但是您可以通過將DataSourceType設置為“Native”來使用它來替代Cursor。你為何這樣做?主要是傾向於將來應用程序升級;通過簡單地將DataSourceType更改為其他選項之一(並可能更改其他一些屬性,如設置連接信息),您可以輕松地切換到其他DBMS,如SQL Server。
當DataSourceType設置為“Native”時,VFP將忽略DataSource。SelectCmd必須是一個SQL SELECT語句,而不是USE命令或表達式,這意味着您總是使用相當於本地視圖的語句,而不是直接使用表。您須確保VFP可找到SELECT語句中引用的任何表,因此如果這些表不在當前目錄中,則需要設置路徑或打開表所屬的數據庫。與往常一樣,如果希望游標可更新,請確保設置更新屬性(KeyFieldList、Tables、UpdateableFieldList和UpdateNameList)。
以下示例(NativeExample.prg)從TestData VFP示例數據庫中的Customer表創建一個可更新的游標:
local loCursor as CursorAdapter, ; laErrors[1] open database (_samples + 'data\testdata') loCursor = createobject('CursorAdapter') with loCursor .Alias = 'customercursor' .DataSourceType = 'Native' .SelectCmd = "select CUST_ID, COMPANY, CONTACT from CUSTOMER " + ; "where COUNTRY = 'Brazil'" .KeyFieldList = 'CUST_ID' .Tables = 'CUSTOMER' .UpdatableFieldList = 'CUST_ID, COMPANY, CONTACT' .UpdateNameList = 'CUST_ID CUSTOMER.CUST_ID, ' + ; 'COMPANY CUSTOMER.COMPANY, CONTACT CUSTOMER.CONTACT' if .CursorFill() browse tableupdate(1) else aerror(laErrors) messagebox(laErrors[2]) endif .CursorFill() endwith close databases all
使用ODBC
ODBC實際上是DataSourceType的四個設置中最直接的一個。將DataSource設置為打開的ODBC連接句柄,設置常用屬性,然后調用CursorFill來檢索數據。如果您填寫KeyFieldList、Tables、UpdateableFieldList和UpdateNameList,VFP將自動生成適當的UPDATE、INSERT和DELETE語句,以便用任何更改更新后端。如果要改用存儲過程,請適當設置*Cmd、*CmdDataSource和*CmdDataSourceType屬性。
下面是一個示例,取自ODBCExample.prg,它調用Northwind數據庫中的CustOrderHist存儲過程,以獲取特定客戶按產品銷售的總單位:
local loCursor as CursorAdapter, ; laErrors[1] loCursor = createobject('CursorAdapter') with loCursor .Alias = 'CustomerHistory' .DataSourceType = 'ODBC' .DataSource = sqlstringconnect('driver=SQL Server;server=(local);' + ; 'database=Northwind;uid=sa;pwd=;trusted_connection=no') .SelectCmd = "exec CustOrderHist 'ALFKI'" if .CursorFill() browse else aerror(laErrors) messagebox(laErrors[2]) endif .CursorFill() endwith
使用ADO
使用ADO作為CursorAdapter的數據訪問機制比使用ODBC有更多的問題:
- 必須將數據源設置為ADO記錄集,該記錄集的ActiveConnection屬性設置為打開的ADO連接對象。
- 如果要使用參數化查詢(這可能是常見情況,而不是檢索所有記錄),則必須將其ActiveConnection屬性設置為open ADO Connection對象的ADO命令對象作為第四個參數傳遞給CursorFill。VFP將負責為您填充Command對象的參數集合(它解析SelectCmd以查找參數),但包含參數值的變量當然必須在作用域中。
- 在數據環境中將一個CursorAdapter與ADO一起使用很簡單:可以將UseDEDataSource設置為.T.。如果願意,可以像使用CursorAdapter一樣設置數據環境的DataSource和DataSourceType屬性。但是,如果數據環境中有多個CursorAdapter,則此操作不起作用。原因是DataEnvironment.DataSource引用的ADO記錄集只能包含一個CursorAdapter的數據;當為第二個CursorAdapter調用CursorFill時,會出現“記錄集已打開”錯誤。因此,如果您的數據環境有多個CursorAdapter,則必須將UseDEDataSource設置為.F.並自己管理每個CursorAdapter的DataSource和DataSourceType屬性(或者可能使用為您管理這些屬性的DataEnvironment子類)。
下面的示例代碼取自ADOExample.prg,它展示了如何在ADO命令對象的幫助下使用參數化查詢檢索數據。這個例子還展示了VFP 8中新的結構化錯誤處理特性的使用;對ADO連接Open方法的調用封裝在一個TRY…CATCH…ENDTRY語句捕獲方法失敗時將引發的COM錯誤。
local loConn as ADODB.Connection, ; loCommand as ADODB.Command, ; loException as Exception, ; loCursor as CursorAdapter, ; lcCountry, ; laErrors[1] loConn = createobject('ADODB.Connection') with loConn .ConnectionString = 'provider=SQLOLEDB.1;data source=(local);' + ; 'initial catalog=Northwind;uid=sa;pwd=;trusted_connection=no' try .Open() catch to loException messagebox(loException.Message) cancel endtry endwith loCommand = createobject('ADODB.Command') loCursor = createobject('CursorAdapter') with loCursor .Alias = 'Customers' .DataSourceType = 'ADO' .DataSource = createobject('ADODB.RecordSet') .SelectCmd = 'select * from customers where country=?lcCountry' lcCountry = 'Brazil' .DataSource.ActiveConnection = loConn loCommand.ActiveConnection = loConn if .CursorFill(.F., .F., 0, loCommand) browse else aerror(laErrors) messagebox(laErrors[2]) endif .CursorFill(.F., .F., 0, loCommand) endwith
使用XML
將XML與CursorAdapter結合使用需要一些額外的東西。以下是問題:
- 數據源屬性被忽略。
- 即使將.F.作為第一個參數傳遞給CursorFill方法,也必須填寫CursorSchema屬性,否則將出現錯誤。
- SelectCmd屬性必須設置為返回游標XML的表達式,例如用戶定義函數(UDF)或對象方法名稱。
- 對游標所做的更改將轉換為diffgram,diffgram是一種XML,它包含更改字段和記錄的之前和之后的值,並在需要更新時放置在UpdateGram屬性中。
- 為了將更改寫回數據源,UpdateCmdDataSourceType必須設置為“XML”,UpdateCmd必須設置為處理更新的表達式(同樣,可能是UDF或對象方法)。您可能需要將“This.UpdateGram”傳遞給UDF,以便它可以將更改發送到數據源。
游標的XML源可以來自不同的地方。例如,可以調用一個UDF,該UDF使用CURSORTOXML()將VFP游標轉換為XML,並返回結果:
use CUSTOMERS cursortoxml('customers', 'lcXML', 1, 8, 0, '1') return lcXML
UDF可以調用返回結果集為XML的Web服務。下面是一個從我在自己的系統上創建和注冊的Web服務中為我生成的自動感應示例(細節並不重要;它只是顯示了一個Web服務的示例)。
local loWS as dataserver web service loWS = NEWOBJECT("Wsclient",HOME()+"ffc\_webservices.vcx") loWS.cWSName = "dataserver web service" loWS = loWS.SetupClient("http://localhost/SQDataServer/dataserver.WSDL", ; "dataserver", "dataserverSoapPort") lcXML = loWS.GetCustomers() return lcXML
它可以使用SQLXML 3.0執行存儲在Web服務器模板文件中的SQL Server 2000查詢(有關SQLXML的更多信息,請訪問http://msdn.microsoft.com並搜索SQLXML)。下面的代碼使用MSXML2.XMLHTTP對象通過HTTP從Northwind Customers表中獲取所有記錄;稍后將詳細解釋這一點。
local loXML as MSXML2.XMLHTTP loXML = createobject('MSXML2.XMLHTTP') loXML.open('POST', 'http://localhost/northwind/template/' + ; 'getallcustomers.xml, .F.) loXML.setRequestHeader('Content-type', 'text/xml') loXML.send() return loXML.responseText
處理更新更為復雜。數據源必須能夠接受和使用diffgram(與SQL Server 2000一樣),或者您必須自己找出更改並發出一系列SQL語句(UPDATE、INSERT和DELETE)來執行更新。
下面是一個示例(XMLExample.prg),它使用帶有XML數據源的CursorAdapter。注意,SelectCmd和UpdateCmd都調用UDF。在SelectCmd的情況下,SQL Server 2000 XML模板的名稱和要檢索的客戶ID被傳遞給一個名為GetNWXML的UDF,稍后我們將討論這個UDF。對於UpdateCmd,VFP將UpdateGram屬性傳遞給SendNWXML,我們稍后也將查看該屬性。
local loCustomers as CursorAdapter, ; laErrors[1] loCustomers = createobject('CursorAdapter') with loCustomers .Alias = 'Customers' .CursorSchema = 'CUSTOMERID C(5), COMPANYNAME C(40), ' + ; 'CONTACTNAME C(30), CONTACTTITLE C(30), ADDRESS C(60), ' + ; 'CITY C(15), REGION C(15), POSTALCODE C(10), COUNTRY C(15), ' + ; 'PHONE C(24), FAX C(24)' .DataSourceType = 'XML' .KeyFieldList = 'CUSTOMERID' .SelectCmd = 'GetNWXML([customersbyid.xml?customerid=ALFKI])' .Tables = 'CUSTOMERS' .UpdatableFieldList = 'CUSTOMERID, COMPANYNAME, CONTACTNAME, ' + ; 'CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX' .UpdateCmdDataSourceType = 'XML' .UpdateCmd = 'SendNWXML(This.UpdateGram)' .UpdateNameList = 'CUSTOMERID CUSTOMERS.CUSTOMERID, ' + ; 'COMPANYNAME CUSTOMERS.COMPANYNAME, ' + ; 'CONTACTNAME CUSTOMERS.CONTACTNAME, ' + ; 'CONTACTTITLE CUSTOMERS.CONTACTTITLE, ' + ; 'ADDRESS CUSTOMERS.ADDRESS, ' + ; 'CITY CUSTOMERS.CITY, ' + ; 'REGION CUSTOMERS.REGION, ' + ; 'POSTALCODE CUSTOMERS.POSTALCODE, ' + ; 'COUNTRY CUSTOMERS.COUNTRY, ' + ; 'PHONE CUSTOMERS.PHONE, ' + ; 'FAX CUSTOMERS.FAX' if .CursorFill(.T.) browse else aerror(laErrors) messagebox(laErrors[2]) endif .CursorFill(.T.) endwith
此代碼引用的XML模板CustomersByID.XML如下所示:
<root xmlns:sql="urn:schemas-microsoft-com:xml-sql"> <sql:header> <sql:param name="customerid"> </sql:param> </sql:header> <sql:query client-side-xml="0"> SELECT * FROM Customers WHERE CustomerID = @customerid FOR XML AUTO </sql:query> </root>
將此文件放在Northwind數據庫的虛擬目錄中(有關配置IIS以使用SQL Server的詳細信息,請參閱附錄)。
這是GetNWXML的代碼。它使用MSXML2.XMLHTTP對象訪問Web服務器上的SQL Server 2000 XML模板並返回結果。模板的名稱(以及可選的任何查詢參數)作為參數傳遞給此代碼。
lparameters tcURL local loXML as MSXML2.XMLHTTP loXML = createobject('MSXML2.XMLHTTP') loXML.open('POST', 'http://localhost/northwind/template/' + tcURL, .F.) loXML.setRequestHeader('Content-type', 'text/xml') loXML.send() return loXML.responseText
SendNWXML看起來很相似,只是它希望傳遞一個diffgram,將diffgram加載到MSXML2.DOMDocument對象中,並將該對象傳遞給Web服務器,然后Web服務器將通過SQLXML將其傳遞給SQL Server 2000進行處理。
lparameters tcDiffGram local loDOM as MSXML2.DOMDocument, ; loXML as MSXML2.XMLHTTP loDOM = createobject('MSXML2.DOMDocument') loDOM.async = .F. loDOM.loadXML(tcDiffGram) loXML = createobject('MSXML2.XMLHTTP') loXML.open('POST', 'http://localhost/northwind/', .F.) loXML.setRequestHeader('Content-type', 'text/xml') loXML.send(loDOM)
要了解其工作原理,請運行XMLExample.prg。您應該在瀏覽窗口中看到一條記錄(ALFKI客戶)。更改某個字段中的值,然后關閉窗口並再次運行PRG。您應該看到您的更改已寫入后端。
CursorAdapter 和 DataEnvironment 子類
與VFP中通常的情況一樣,我創建了CursorAdapter和DataEnvironment的子類,我將使用這些子類而不是基類。
SFCursorAdapter
SFCursorAdapter(在SFDataClasses.vcx中)是CursorAdapter的一個子類,它添加了一些附加功能:
- 它可以自動處理參數化查詢;您可以將參數值定義為靜態(常量值)或動態(表達式,例如“=Thisform.txtName.value”,在打開或刷新游標時計算)。
- 它可以在游標打開后自動創建索引。
- 它為ADO做了一些特殊的事情,例如將數據源設置為ADO記錄集,將記錄集的ActiveConnection屬性設置為ADO連接對象,以及在使用參數化查詢時創建ADO命令對象並將其傳遞給CursorFill。
- 它提供了一些簡單的錯誤處理(cErrorMessage屬性用錯誤消息填充)。
- 它有CursorAdapter中缺少的更新和發布方法。
讓我們來看看這個類。
Init方法創建兩個集合(使用新的集合基類,它維護事物的集合),一個用於SelectCmd屬性可能需要的參數,另一個用於在游標打開后應自動創建的標記。它還設置了MULTILOCKS on,因為這是CursorAdapter游標所必需的。
with This * 創建參數和標記集合 .oParameters = createobject('Collection') .oTags = createobject('Collection') * 確保 MULTILOCKS 設置為 on. set multilocks on endwith
AddParameter方法向parameters集合添加一個參數。向此方法傳遞參數的名稱(該名稱應與SelectCmd屬性中顯示的名稱匹配)和可選的參數值(如果現在不傳遞,可以稍后使用GetParameter方法進行設置)。這段代碼展示了VFP 8中的兩個新特性:新的Empty類(沒有PEMs),使其成為輕量級對象的理想選擇;ADDPROPERTY()函數(其作用類似於那些沒有該方法的對象的ADDPROPERTY方法)。
lparameters tcName, ; tuValue local loParameter loParameter = createobject('Empty') addproperty(loParameter, 'Name', tcName) addproperty(loParameter, 'Value', tuValue) This.oParameters.Add(loParameter, tcName)
使用GetParameter方法返回一個特定的參數對象;當您想設置要用於參數的值時,通常會使用這個方法。
lparameters tcName local loParameter loParameter = This.oParameters.Item(tcName) return loParameter
SetConnection方法用於將DataSource屬性設置為所需的連接。如果DataSourceType是“ODBC”,請傳遞連接句柄。如果是“ADO”,則數據源需要是一個ADO記錄集,其ActiveConnection屬性設置為打開的ADO連接對象,因此通過Connection對象,SetConnection將創建記錄集並將其ActiveConnection設置為傳遞對象。
lparameters tuConnection with This do case case .DataSourceType = 'ODBC' .DataSource = tuConnection case .DataSourceType = 'ADO' .DataSource = createobject('ADODB.RecordSet') .DataSource.ActiveConnection = tuConnection endcase endwith
要創建游標,請調用GetData方法而不是CursorFill,因為它會自動處理參數和錯誤。如果要創建游標但不填充數據,請將.T.傳遞給GetData。此方法所做的第一件事是創建私有范圍的變量,這些變量的名稱和值與參數集合中定義的參數相同(從這里調用的GetParameterValue方法返回參數對象的值或以“=”開頭的值的求值)。接下來,如果我們使用ADO並且有任何參數,代碼將創建一個ADO Command對象並將其ActiveConnection設置為Connection對象,然后將Command對象傳遞給CursorFill方法;CursorAdapter要求在參數化ADO查詢中使用該方法。如果我們沒有使用ADO或者沒有任何參數,代碼只調用cursor fill來填充游標。注意.T.被傳遞給CursorFill,告訴它在CursorSchema被填充時使用CursorSchema(這是我希望基類具有的行為)。如果創建了游標,則代碼調用CreateTags方法為游標創建所需的索引;如果沒有,則調用HandleError方法來處理發生的任何錯誤。
lparameters tlNoData local loParameter, ; lcName, ; luValue, ; llUseSchema, ; loCommand, ; llReturn with This *如果我們要填充游標(而不是創建空游標),則創建變量來保存任何參數 *必須在這里而不是在方法中這樣做,因為我們希望它們的作用域是私有的 if not tlNoData for each loParameter in .oParameters lcName = loParameter.Name luValue = .GetParameterValue(loParameter) store luValue to (lcName) next loParameter endif not tlNoData *若使用ADO且有參數,則需一個Command對象來處理這個問題 llUseSchema = not empty(.CursorSchema) if '?' $ .SelectCmd and (.DataSourceType = 'ADO' or (.UseDEDataSource and ; .Parent.DataSourceType = 'ADO')) loCommand = createobject('ADODB.Command') loCommand.ActiveConnection = iif(.UseDEDataSource, ; .Parent.DataSource.ActiveConnection, .DataSource.ActiveConnection) llReturn = .CursorFill(llUseSchema, tlNoData, .nOptions, loCommand) else *嘗試填充游標 llReturn = .CursorFill(llUseSchema, tlNoData, .nOptions) endif '?' $ .SelectCmd ... *如果我們創建了游標,請創建為其定義的任何標記。 *如果沒有,請處理錯誤。 if llReturn .CreateTags() else .HandleError() endif llReturn endwith return llReturn
Update方法很簡單:它只調用TABLEUPDATE()嘗試更新原始數據源,如果失敗則調用HandleError。
local llReturn llReturn = tableupdate(1, .F., This.Alias) if not llReturn This.HandleError() endif not llReturn return llReturn
有幾種方法我們在這里不看,你可以自己檢查一下。AddTag將游標創建后要創建的索引的信息添加到tags集合,而CreateTags(從GetData調用)在INDEX ON語句中使用該集合中的信息。HandleError使用AERROR()來確定出錯的地方,並將錯誤數組的第二個元素放入cErrorMessage屬性中。
讓我們看幾個使用這個類的例子。第一個(取自TestCursorAdapter.prg)從Northwind數據庫的Customers表中獲取所有記錄。這段代碼與用於基類CursorAdapter的代碼沒有太大的不同(由於沒有填寫CursorSchema,因此必須將.F.作為第一個參數傳遞給CursorFill)。
loCursor = newobject('SFCursorAdapter', 'SFDataClasses') with loCursor *連接到SQL Server Northwind數據庫並獲取客戶記錄 .DataSourceType = 'ODBC' .DataSource = sqlstringconnect('driver=SQL Server;server=(local);' + ; 'database=Northwind;uid=sa;pwd=;trusted_connection=no') .Alias = 'Customers' .SelectCmd = 'select * from customers' if .GetData() browse else messagebox('Could not get the data. The error message was:' + ; chr(13) + chr(13) + .cErrorMessage) endif .GetData() endwith
下一個示例(也取自TestCursorAdapter.prg)使用SFConnectionMgr的ODBC版本來管理連接,我們在“VFP中的數據策略:基礎篇”一文中查看了該版本。它還為SelectCmd使用參數化語句,顯示AddParameter方法如何允許您處理參數,並演示如何使用AddTag方法自動為游標創建標記。
loConnMgr = newobject('SFConnectionMgrODBC', 'SFRemote') with loConnMgr .cDriver = 'SQL Server' .cServer = '(local)' .cDatabase = 'Northwind' .cUserName = 'sa' .cPassword = '' endwith if loConnMgr.Connect() loCursor = newobject('SFCursorAdapter', 'SFDataClasses') with loCursor .DataSourceType = 'ODBC' .SetConnection(loConnMgr.GetConnection()) .Alias = 'Customers' .SelectCmd = 'select * from customers where country = ?pcountry' .AddParameter('pcountry', 'Brazil') .AddTag('CustomerID', 'CustomerID') .AddTag('Company', 'upper(CompanyName)') .AddTag('Contact', 'upper(ContactName)') if .GetData() messagebox('Brazilian customers in CustomerID order') set order to CustomerID go top browse messagebox('Brazilian customers in Contact order') set order to Contact go top browse messagebox('Canadian customers') loParameter = .GetParameter('pcountry') loParameter.Value = 'Canada' .Requery() browse else messagebox('Could not get the data. The error message was:' + ; chr(13) + chr(13) + .cErrorMessage) endif .GetData() endwith else messagebox(loConnMgr.cErrorMessage) endif loConnMgr.Connect()
SFDataEnvironment
SFDataEnvironment(也在SFDataClasses.vcx中)比SFCursorAdapter簡單得多,但添加了一些有用的功能:
- GetData方法調用所有SFCursorAdapter成員的GetData方法,因此不必單獨調用它們。
- 類似地,Requery和Update方法調用每個SFCursorAdapter成員的Requery和Update方法。
- 與SFCursorAdapter類似,SetConnection方法將數據源設置為ADO記錄集,並將記錄集的ActiveConnection屬性設置為ADO連接對象。但是,它也調用UseDEDataSource設置為.F.的任何SFCursorAdapter成員的SetConnection方法。
- 它提供了一些簡單的錯誤處理(用錯誤消息填充cErrorMessage屬性)。
- 它有一個Release方法。
GetData非常簡單:它只調用具有該方法的任何成員對象的GetData方法。
lparameters tlNoData local loCursor, ; llReturn for each loCursor in This.Objects if pemstatus(loCursor, 'GetData', 5) llReturn = loCursor.GetData(tlNoData) if not llReturn This.cErrorMessage = loCursor.cErrorMessage exit endif not llReturn endif pemstatus(loCursor, 'GetData', 5) next loCursor return llReturn
SetConnection稍微復雜一點:它調用任何具有該方法且UseDEDataSource設置為.F.的成員對象的SetConnection方法,然后使用類似於SFCursorAdapter中的代碼設置自己的數據源(如果任何CursorAdapter的UseDEDataSource設置為.T.)。
lparameters tuConnection local llSetOurs, ; loCursor, ; llReturn with This *調用任何不使用數據源的CursorAdapter的SetConnection方法 llSetOurs = .F. for each loCursor in .Objects do case case upper(loCursor.BaseClass) <> 'CURSORADAPTER' case loCursor.UseDEDataSource llSetOurs = .T. case pemstatus(loCursor, 'SetConnection', 5) loCursor.SetConnection(tuConnection) endcase next loCursor *如果發現使用數據源的CursorAdapter,需要設置數據源 if llSetOurs do case case .DataSourceType = 'ODBC' .DataSource = tuConnection case .DataSourceType = 'ADO' .DataSource = createobject('ADODB.RecordSet') .DataSource.ActiveConnection = tuConnection endcase endif llSetOurs endwith
Requery和Update幾乎與GetData相同,所以我們不必費心去查看它們。
TestDE.prg顯示了如何使用SFDataEnvironment作為兩個SFCursorAdapter類的容器。由於此示例使用ADO,因此每個SFCursorAdapter都需要自己的數據源,故UseDEDataSource設置為.F.。請注意,對DataEnvironment SetConnection方法的單個調用負責為每個CursorAdapter設置數據源屬性。
loConnMgr = newobject('SFConnectionMgrADO', 'SFRemote') with loConnMgr .cDriver = 'SQLOLEDB.1' .cServer = '(local)' .cDatabase = 'Northwind' .cUserName = 'sa' .cPassword = '' endwith if loConnMgr.Connect() loDE = newobject('SFDataEnvironment', 'SFDataClasses') with loDE .NewObject('CustomersCursor', 'SFCursorAdapter', 'SFDataClasses') with .CustomersCursor .Alias = 'Customers' .SelectCmd = 'select * from customers' .DataSourceType = 'ADO' endwith .NewObject('OrdersCursor', 'SFCursorAdapter', 'SFDataClasses') with .OrdersCursor .Alias = 'Orders' .SelectCmd = 'select * from orders' .DataSourceType = 'ADO' endwith .SetConnection(loConnMgr.GetConnection()) if .GetData() select Customers browse nowait select Orders browse else messagebox('Could not get the data. The error message was:' + ; chr(13) + chr(13) + .cErrorMessage) endif .GetData() endwith else messagebox(loConnMgr.cErrorMessage) endif loConnMgr.Connect()
可重用數據類
現在我們有了CursorAdapter和DataEnvironment子類,讓我們討論一下可重用的數據類。
VFP開發人員要求微軟在VFP中添加的一件事是可重用的數據環境。例如,您可能有一個表單和一個報表具有完全相同的數據設置,但是您必須手動為每個表單和報表填充數據環境,因為數據環境是不可重用的。一些開發人員(以及幾乎所有的框架供應商)通過在代碼中創建數據環境(它們不能可視化地被子類化)並在表單上使用“loader”對象來實例化數據環境子類,使得創建可重用的數據環境變得更加容易。然而,這是一種混亂,並沒有幫助報告。
現在,在VFP 8中,我們能夠創建兩個可重用的數據類,它們可以提供從任何數據源到任何需要它們的數據源的游標,以及可重用的數據環境,后者可以托管數據類。在撰寫本文時,您不能在報表中使用CursorAdapter或DataEnvironment子類,但可以通過編程添加CursorAdapter子類(例如在DataEnvironment的Init方法中)來利用那里的可重用性。
我們來為Northwind客戶和訂單表創建數據類。首先,創建SFCursorAdapter的一個子類CustomersCursor並設置屬性,如下所示。
屬性 | 值 |
Alias | Customers |
CursorSchema | CUSTOMERID C(5), COMPANYNAME C(40), CONTACTNAME C(30), CONTACTTITLE C(30), ADDRESS C(60), CITY C(15), REGION C(15), POSTALCODE C(10), COUNTRY C(15), PHONE C(24), FAX C(24) |
KeyFieldList | CUSTOMERID |
SelectCmd | select * from customers |
Tables | CUSTOMERS |
UpdatableFieldList | CUSTOMERID, COMPANYNAME, CONTACTNAME, CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX |
UpdateNameList | CUSTOMERID CUSTOMERS.CUSTOMERID, COMPANYNAME CUSTOMERS.COMPANYNAME, CONTACTNAME CUSTOMERS.CONTACTNAME, CONTACTTITLE CUSTOMERS.CONTACTTITLE, ADDRESS CUSTOMERS.ADDRESS, CITY CUSTOMERS.CITY, REGION CUSTOMERS.REGION, POSTALCODE CUSTOMERS.POSTALCODE, COUNTRY CUSTOMERS.COUNTRY, PHONE CUSTOMERS.PHONE, FAX, CUSTOMERS.FAX |
備注:您可以使用CursorAdapter生成器完成大部分工作,特別是設置CursorSchema和更新屬性。訣竅是打開“use connection settings in builder only”(僅在生成器中使用連接設置)選項,填寫連接信息以建立實時連接,然后填寫SelectCmd並使用生成器為您構建其余屬性。
現在,只要您需要Northwind Customers表中的記錄,就只需使用CustomersCursor類。當然,我們還沒有定義任何連接信息,但這實際上是件好事,因為這個類不必擔心如何獲取數據(ODBC、ADO或XML),甚至不必擔心要使用什么數據庫引擎(用於SQL Server、Access和新版VFP8的Northwind數據庫)。
但是請注意,這個游標涉及Customers表中的所有記錄。有時候,你只想要一個特定的客戶。所以,讓我們創建一個CustomersCursor的子類CustomerByIDCursor。將SelectCmd更改為“select * from customers where customerid = ?pcustomerid”並將以下代碼放入Init:
lparameters tcCustomerID dodefault() This.AddParameter('pCustomerID', tcCustomerID)
這將創建一個名為pCustomerID的參數(與SelectCmd中指定的名稱相同),並將其設置為傳遞的任意值。如果未傳遞任意值,請使用GetParameter返回此參數的對象,並在調用GetData之前設置其Value屬性。
創建一個類似於CustomersCursor的orderscorsor類,只是它從Orders表中檢索所有記錄。然后創建一個OrdersForCustomerCursor子類,該子類只檢索特定客戶的訂單。將SelectCmd設置為“select * from orders where customerid = ?pcustomerid”,並將與CustomerByIDCursor相同的代碼放入Init(因為它是相同的參數)。
要測試其效果,請運行TestCustomersCursor.prg。
示例:Form
現在我們有了一些可重用的數據類,來用一下它們。首先,讓我們創建一個名為CustomersAndOrdersDataEnvironment的SFDataEnvironment子類,它包含CustomerByIDCursor和OrdersForCustomerCursor類。將AutoOpenTables設置為.F.(因為我們需要在打開表之前設置連接信息),並將CursorAdapter和UseDEDataSource設置為.T.。現在可以以某種形式使用此數據環境來顯示有關特定客戶的信息,包括其訂單。
讓我們創建這樣一個表單。創建一個名為CustomerOrders.scx的表單(它包含在本文檔附帶的示例文件中),將DEClass和DEClassLibrary設置為CustomersAndOrdersDataEnvironment,以便我們使用可重用的數據環境。將以下代碼放入Load方法中:
#define ccDATASOURCETYPE 'ADO' with This.CustomersAndOrdersDataEnvironment *設置數據環境數據源 .DataSourceType = ccDATASOURCETYPE *如果我們使用ODBC或ADO,請創建一個連接管理器 *並打開連接到Northwind數據庫的連接 if .DataSourceType $ 'ADO,ODBC' This.AddProperty('oConnMgr') This.oConnMgr = newobject('SFConnectionMgr' + ccDATASOURCETYPE, ; 'SFRemote') with This.oConnMgr .cDriver = iif(ccDATASOURCETYPE = 'ADO', 'SQLOLEDB.1', ; 'SQL Server') .cServer = '(local)' .cDatabase = 'Northwind' .cUserName = 'sa' .cPassword = '' endwith if not This.oConnMgr.Connect() messagebox(oConnMgr.cErrorMessage) return .F. endif not This.oConnMgr.Connect() *如果我們使用ADO,每個游標都必須有自己的數據源 if .DataSourceType = 'ADO' .CustomerByIDCursor.UseDEDataSource = .F. .CustomerByIDCursor.DataSourceType = 'ADO' .OrdersForCustomerCursor.UseDEDataSource = .F. .OrdersForCustomerCursor.DataSourceType = 'ADO' endif .DataSourceType = 'ADO' *將數據源設置為連接 .SetConnection(This.oConnMgr.GetConnection()) *如果使用的是XML,請更改SelectCmd以調用GetNWXML函數 else .CustomerByIDCursor.SelectCmd = 'GetNWXML([customersbyid.xml?' + ; 'customerid=] + pCustomerID)' .CustomerByIDCursor.UpdateCmdDataSourceType = 'XML' .CustomerByIDCursor.UpdateCmd = 'SendNWXML(This.UpdateGram)' .OrdersForCustomerCursor.SelectCmd = 'GetNWXML([ordersforcustomer.' + ; 'xml?customerid=] + pCustomerID)' .OrdersForCustomerCursor.UpdateCmdDataSourceType = 'XML' .OrdersForCustomerCursor.UpdateCmd = 'SendNWXML(This.UpdateGram)' endif .DataSourceType $ 'ADO,ODBC' *指定將從CustomerID文本框中填充游標參數的值 loParameter = .CustomerByIDCursor.GetParameter('pCustomerID') loParameter.Value = '=Thisform.txtCustomerID.Value' loParameter = .OrdersForCustomerCursor.GetParameter('pCustomerID') loParameter.Value = '=Thisform.txtCustomerID.Value' *創建空游標並在失敗時顯示錯誤消息 if not .GetData(.T.) messagebox(.cErrorMessage) return .F. endif not .GetData(.T.) endwith
這看起來像很多代碼,但其中大部分是為了演示目的,以允許切換到不同的數據訪問機制。
此代碼創建一個連接管理器來處理連接(ADO、ODBC或XML),具體取決於ccDATASOURCETYPE常量,您可以更改該常量以嘗試每個機制。對於ADO,由於每個CursorAdapter都必須有自己的數據源,因此為每個CursorAdapter設置UseDEDataSource和DataSourceType屬性。然后,代碼調用SetConnection方法來設置連接信息。對於XML,SelectCmd、UpdateCmdDataSourceType和UpdateCmd屬性必須如前所述進行更改。接下來,代碼使用兩個CursorAdapter對象的GetParameter方法將pCustomerID參數的值設置為表單中文本框的內容。注意在值中使用“=”;這意味着每次需要時都會對Value屬性求值,因此我們基本上有一個動態參數(當用戶在文本框中鍵入時,保存將參數不斷更改為當前值的需要)。最后,調用GetData方法來創建空游標,以便控件的數據綁定可以工作。
在表單上放置一個文本框並將其命名為txtCustomer,將以下代碼放入其Valid方法中:
with Thisform .CustomersAndOrdersDataEnvironment.Requery() .Refresh() endwith
這將導致在輸入客戶ID時重新查詢游標和刷新控件。
在表單上放置一個標簽,放在文本框旁邊,並將其標題設置為“客戶ID”。
將CompanyName、ContactName、Address、City、Region、PostalCode和Country字段從DataEnvironment中的Customers游標拖動到表單中,以創建這些字段的控件。然后在Orders游標中選擇OrderID、EmployeeID、OrderDate、RequiredDate、ShippedDate、ShipVia和Freight字段,並將它們拖到表單中以創建網格(Grid--譯者注)。
就這樣子。運行表單並輸入“ALFKI”作為客戶ID。當您在文本框中選擇選項卡時,您應該會看到客戶地址信息和訂單。嘗試更改有關客戶或訂單的內容,然后關閉表單,再次運行它,然后再次輸入“ALFKI”。您應該看到,您所做的更改已寫入后端數據庫,而無需您付出任何努力。
很酷吧?這比基於本地表或視圖創建表單要簡單得多。更好的方法是,嘗試將ccDATASOURCETYPE常量更改為“ADO”或“XML”,並注意表單的外觀和工作方式完全相同。這就是CursorAdapters的要點!
示例:Report
我們試一個Report。此處討論的示例取自此文檔附帶的CustomerOrders.frx。這里最大的問題是,與表單不同,我們不能告訴報表使用DataEnvironment子類,也不能在DataEnvironment中刪除CursorAdapter子類。因此,我們必須在報表中放入一些代碼,以便將CursorAdapter子類添加到數據環境中。盡管將此代碼放入報表數據環境的BeforeOpenTables事件中似乎是合乎邏輯的,但實際上這不會起作用,因為我不明白為什么,在預覽報表時,BeforeOpenTables會在每個頁面上激發。所以,我們將把代碼放入Init方法中。
#define ccDATASOURCETYPE 'ODBC' with This set safety off *設置數據環境數據源 .DataSourceType = ccDATASOURCETYPE *為客戶和訂單創建CursorAdapter對象 .NewObject('CustomersCursor', 'CustomersCursor', 'NorthwindDataClasses') .CustomersCursor.AddTag('CustomerID', 'CustomerID') .NewObject('OrdersCursor', 'OrdersCursor', 'NorthwindDataClasses') .OrdersCursor.AddTag('CustomerID', 'CustomerID') *若使用ODBC或ADO,請創建一個連接管理器 *並打開連接到Northwind數據庫的連接 if .DataSourceType $ 'ADO,ODBC' .AddProperty('oConnMgr') .oConnMgr = newobject('SFConnectionMgr' + ccDATASOURCETYPE, ; 'SFRemote') with .oConnMgr .cDriver = iif(ccDATASOURCETYPE = 'ADO', 'SQLOLEDB.1', ; 'SQL Server') .cServer = '(local)' .cDatabase = 'Northwind' .cUserName = 'sa' .cPassword = '' endwith if not .oConnMgr.Connect() messagebox(.oConnMgr.cErrorMessage) return .F. endif not .oConnMgr.Connect() *如果使用ADO,每個游標都必須有自己的數據源 if .DataSourceType = 'ADO' .CustomersCursor.UseDEDataSource = .F. .CustomersCursor.DataSourceType = 'ADO' .CustomersCursor.SetConnection(.oConnMgr.GetConnection()) .OrdersCursor.UseDEDataSource = .F. .OrdersCursor.DataSourceType = 'ADO' .OrdersCursor.SetConnection(.oConnMgr.GetConnection()) else .CustomersCursor.UseDEDataSource = .T. .OrdersCursor.UseDEDataSource = .T. .DataSource = .oConnMgr.GetConnection() endif .DataSourceType = 'ADO' .CustomersCursor.SetConnection(.oConnMgr.GetConnection()) .OrdersCursor.SetConnection(.oConnMgr.GetConnection()) *若使用XML,請更改SelectCmd以調用GetNWXML函數 else .CustomersCursor.SelectCmd = 'GetNWXML([getallcustomers.xml])' .CustomersCursor.DataSourceType = 'XML' .OrdersCursor.SelectCmd = 'GetNWXML([getallorders.xml])' .OrdersCursor.DataSourceType = 'XML' endif .DataSourceType $ 'ADO,ODBC' *獲取數據並在失敗時顯示錯誤消息 if not .CustomersCursor.GetData() messagebox(.CustomersCursor.cErrorMessage) return .F. endif not .CustomersCursor.GetData() if not .OrdersCursor.GetData() messagebox(.OrdersCursor.cErrorMessage) return .F. endif not .OrdersCursor.GetData() *設置從客戶到訂單的關系 set relation to CustomerID into Customers endwith
此代碼看起來與窗體的代碼類似。同樣,大多數代碼是處理不同的數據訪問機制。但是,還有一些額外的代碼,因為我們不能使用DataEnvironment子類,必須自己編寫行為代碼。
現在,我們如何方便地把字段放在Report上?由於CursorAdapter在設計時不存在於數據環境中,因此我們不能將字段從它們拖到Report中。這里有一個提示:創建一個PRG來創建游標並將其留在作用域中(通過掛起或使CursorAdapter對象公開),然后使用Quick Report函數將具有適當大小的字段放在Report上。
在CUSTOMERS.CUSTOMERID上創建一個組並選中“在新頁面上啟動每個組”。然后將Report布局為類似於以下內容:
XMLAdapter
除了CursorAdapter之外,VFP 8還有三個新的基類來改進VFP對XML的支持:XMLAdapter、XMLTable和XMLField。XMLAdapter提供了一種在XML和VFP游標之間轉換數據的方法。它的功能比CursorToXML()和XMLToCursor()函數多得多,包括支持分層XML和使用那些函數不支持的XML類型(如ADO.NET數據集)的功能。XMLTable和XMLField是子對象,它們提供微調XML數據的模式的能力。此外,XMLTable還有一個ApplyDiffgram方法,它允許VFP使用updategrams和diffgrams,這是VFP 7中缺少的。
為了讓您了解它的功能,我創建了一個返回ADO.NET數據集的ASP.NET Web服務,然后使用VFP中的XMLAdapter對象來使用該數據集。現在我做到了。
首先,在Visual Studio.NET中,我將Northwind Customers表從服務器資源管理器拖到一個名為NWWebService的新ASP.NET Web服務項目中。這會自動創建兩個對象,SQLConnection1和SQLDataAdapter1。然后,我將以下代碼添加到現有生成的代碼中:
<WebMethod()> Public Function GetAllCustomers() As DataSet Dim loDataSet As New DataSet() Me.SqlConnection1.Open() Me.SqlDataAdapter1.Fill(loDataSet) Return loDataSet End Function
我構建該項目是為了在NWWebService虛擬目錄(VS.NET自動為我創建)中生成適當的Web服務文件。
為了在VFP中使用這個Web服務,我使用IntelliSense管理器注冊了一個名為“Northwind.NET”的Web服務,指向“http://localhost/NWWebService/NWWebService.asmx?WSDL”作為WSDL文件的位置。然后我創建了以下代碼(在XMLAdapterWebService.prg中)來調用Web服務並將ADO.NET數據集轉換為VFP游標。
local loWS as Northwind.NET, ; loXMLAdapter as XMLAdapter, ; loTable as XMLTable *從.NET Web服務獲取.NET數據集 loWS = NEWOBJECT("Wsclient",HOME()+"ffc\_webservices.vcx") loWS.cWSName = "Northwind.NET" loWS = loWS.SetupClient("http://localhost/NWWebService/NWWebService.asmx" + ; "?WSDL", "NWWebService", "NWWebServiceSoap") loXML = loWS.GetAllCustomers() *創建一個XMLAdapter並加載數據 loXMLAdapter = createobject('XMLAdapter') loXMLAdapter.XMLSchemaLocation = '1' loXMLAdapter.LoadXML(loXML.Item(0).parentnode.xml) *如果成功地加載了XML,那么從每個表對象創建並瀏覽一個游標 if loXMLAdapter.IsLoaded for each loTable in loXMLAdapter.Tables loTable.ToCursor() browse use next loTable endif loXMLAdapter.IsLoaded
注意,為了使用XMLAdapter,您需要在系統上安裝MSXML 4.0服務包1或更高版本。您可以從MSDN網站下載(http://MSDN.microsoft.com並搜索MSXML)。
總結
我認為CursorAdapter是VFP 8中最大和最令人興奮的增強之一,因為它提供了一個一致且易於使用的遠程數據接口,而且它允許我們創建可重用的數據類。我相信一旦你用它來工作,你會發現他們和我一樣令人興奮。
作者介紹:
Doug Hennig是Stonefield Systems Group Inc.的合作伙伴。他是獲獎的Stonefield數據庫工具包(SDT)的作者和獲獎的Stonefield查詢的共同作者。他是《黑客視覺FoxPro 7.0指南》的合著者(與Tamar Granor、Ted Roche和Della Martin一起)和《視覺FoxPro 7.0的新特性》的合著者(與Tamar Granor和Kevin McNeish一起),均來自Hentzenwerke出版社,在Pinnacle Publishing的Pros Talk VisualFoxPro系列中,“VisualFoxPro數據字典”的作者。他在FoxTalk上寫了每月的“可重用工具”專欄。他是《黑客指南》和《基礎知識》的技術編輯,這兩本書都來自亨森沃克出版社。自1997年以來,道格在每次微軟FoxPro開發者大會(DevCon)以及北美各地的用戶團體和開發者大會上都發表過演講。他是微軟最有價值的專業人士(MVP)和認證專業人士(MCP)。
附錄:設置SQL Server 2000 XML訪問存取
另文,本文略……