VFP的數據策略:高級篇

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”頁面允許您維護DataEnvironmentCursorAdapter成員(遊標對象不會在生成器中顯示,也不能添加它們)。Add按鈕允許您向DataEnvironment添加CursorAdapter子類,而New則創建一個新的基類CursorAdapterRemove刪除Select CursorAdapterBuilder爲所選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訪問存取

另文,本文略……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章