第四節(jié) 使用 sql pass-through
與使用視圖相比的另一個(gè)選擇是單獨(dú)地使用 sql pass-through。這就意味著你發(fā)送 sql 語(yǔ)句到后端并明白地告訴它要做什么。如果你想添加一條記錄可發(fā)送一個(gè) insert。要保存記錄可發(fā)送一個(gè) update。顯然這比使用視圖要做更多的工作。但是它允許你完全地控制發(fā)生什么和什么時(shí)候?qū)?huì)發(fā)生。
載入表單
表單 membexec.scx 是一個(gè)與上面的表單相似的表單,它只是使用 sql pass-through 而不是視圖。以下是表單的 load 方法代碼。
open database library
thisform.nhandle = sqlconnect('cnlibrary')
if thisform.nhandle < 0
thisform.showerror
thisform.lconnected = .f.
else
lcsql = "select member.member_no, lastname, firstname, " + ;
" middleinitial, street, city, state, zip, " + ;
" phone_no, expr_date, birth_date = null, " + ;
" adult_member_no = null " + ;
"from member, adult " + ;
"where member.member_no = -99 "
if sqlexec(thisform.nhandle, lcsql, "c_member") < 0
thisform.showerror
thisform.lconnected = .f.
endif
= cursorsetprop("buffering", db_bufoptrecord, 'c_member')
endif
在表單載入時(shí),sqlconnect() 用于使用保存在 visual foxpro library 數(shù)據(jù)庫(kù)中的 cnlibrary 連接來(lái)建立到 sql server 的連接。如果 sqlconnect() 失敗,除了退出和回家外,你沒(méi)有別的事可做。
如果連接正常,以發(fā)送到 sql server 一個(gè)搜索成員號(hào) -99 的 select 語(yǔ)句來(lái)創(chuàng)建了一個(gè)空的游標(biāo)。即使沒(méi)有返回記錄,visual foxpro 也會(huì)創(chuàng)建該游標(biāo)。然后設(shè)置該游標(biāo)的開(kāi)放式緩存。這樣做的原因是可以在該表單上使用緩存了的游標(biāo)。這反映了一些視圖的易用性。
定位一個(gè)成員
當(dāng)在表單上使用視圖時(shí),定位一個(gè)成員只需簡(jiǎn)單地為視圖參數(shù)賦值并重新獲取值(requery)。當(dāng)使用 sql pass-through 時(shí)要稍稍復(fù)雜一些。要獲取成員信息,要?jiǎng)?chuàng)建一條 select 語(yǔ)句并發(fā)送到服務(wù)器。在下面的代碼中你可以看到這是一個(gè) union select。如果成員存在于 member 表和 adult 或 juvenile 表中。
lcsql = "select member.member_no, lastname, firstname, " + ;
" middleinitial, street, city, state, zip, " + ;
" phone_no, expr_date, birth_date = null, " + ;
" adult_member_no = null " + ;
"from member, adult " + ;
"where member.member_no = adult.member_no " + ;
" and member.member_no = " + ;
" alltrim(thisform.txtmemberid.value) + " " + ;
"union " + ;
"select member.member_no, lastname, firstname, " + ;
" middleinitial, street, city, state, zip, " + ;
" phone_no, expr_date, birth_date, " + ;
" adult_member_no " + ;
"from member, adult, juvenile " + ;
"where member.member_no = juvenile.member_no " + ;
" and adult.member_no = " + ;
" juvenile.adult_member_no " + ;
" and member.member_no = " + ;
" alltrim(thisform.txtmemberid.value)
if sqlexec(thisform.nhandle, lcsql, "c_member") < 0
<略去的代碼>
如果 c_member 游標(biāo)為空則沒(méi)有輸入的 id 成員存在。否則所有成員信息都在游標(biāo)中。游標(biāo)設(shè)置了行緩存,且表單控件用游標(biāo)中的成員信息填充。
union 允許你發(fā)送一個(gè) select 到服務(wù)器并獲取該成員的所有信息。在早期的示例中,需要對(duì)兩個(gè)或三個(gè)視圖進(jìn)行重查詢(requery) 而不是象這里一樣只需要一個(gè) sqlexec()。注意,如果你使用視圖設(shè)計(jì)器,你不能創(chuàng)建一個(gè)帶 union 的遠(yuǎn)程視圖。但你可以用 create sql view 命令來(lái)創(chuàng)建這種視圖。
添加一個(gè)成人
當(dāng)用戶按下 add 按鈕時(shí),一條空白的記錄添加到 c_member 游標(biāo)中。這與早期的視圖示例不同。
select c_member
= tablerevert()
append blank
僅出于可讀性的原因,添加新成員的代碼位于表單的 addmember 方法中。因?yàn)樘砑右粋€(gè)成員包括向兩個(gè)表添加記錄,因此開(kāi)始一個(gè)事務(wù)處理。只在使用視圖的表單中,使用 sqlsetprop() 函數(shù)來(lái)開(kāi)始事務(wù)處理。
= sqlsetprop(thisform.nhandle, "transactions", 2)
當(dāng)用視圖來(lái)訪問(wèn)遠(yuǎn)程數(shù)據(jù)時(shí),你可以依靠 visual foxpro 來(lái)為你處理大多數(shù)的后臺(tái)工作。例如,在早期的表單中,你看到了要發(fā)送一個(gè) insert 或 update 到服務(wù)器,你只需要發(fā)布一條 tableupdate()。insert 或 update 的語(yǔ)法都由 visual foxpro 為你做了。
這里的表單合作 sqlexec() 函數(shù)來(lái)發(fā)送 sql 語(yǔ)句到服務(wù)器。這就意味著你必須自己構(gòu)造 sql 語(yǔ)句。在開(kāi)始事務(wù)處理后,構(gòu)造一個(gè) insert 語(yǔ)句來(lái)添加新記錄到 member 表中。
* 添加新成員到 member 表
lcsql = "insert member (lastname, firstname, " + ;
"middleinitial, photograph) " + ;
"values ('" + ;
alltrim(thisform.txtfirstname.value) + ;
"', '" + ;
alltrim(thisform.txtlastname.value) + ;
"', '" + ;
alltrim(thisform.txtmiddleinitial.value) + ;
"', " + "null)"
if sqlexec(thisform.nhandle, lcsql) < 0
thisform.showerror
* rollback the transaction
= sqlrollback(thisform.nhandle)
return
endif
你現(xiàn)在需要知道 sql server 為新成員指定的 member_no。代碼與早期表單相似。
* 找出新成員的 member_no
if sqlexec(thisform.nhandle, "select @@identity") < 0
<略去的代碼>
nnewmemberid = sqlresult.exp
然后構(gòu)造一個(gè) insert 來(lái)添加新記錄到 adult 表。從服務(wù)器獲取的 @@identity 值用于該 insert 中來(lái)正確地連接 adult 和 member 中的記錄。
* 添加新成員到 adult 表
lcsql = "insert adult (member_no, street, city, state, " + ;
"zip, phone_no, expr_date) " + ;
"values (" + alltrim(str(nnewmemberid)) + ", '" +;
alltrim(thisform.txtstreet.value) + ;
"', '" + ;
alltrim(thisform.txtcity.value) + ;
"', '" + ;
alltrim(thisform.txtstate.value) + ;
"', '" + ;
alltrim(thisform.txtzip.value) + ;
"', '" + ;
alltrim(thisform.txtphonenumber.value) + ;
"', "'" + ;
ttoc(dtot(gomonth(date(),12))) + "' )"
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代碼>
如前所述,如果一切正常,則提交事務(wù)處理否則回滾。
保存修改
保存已存在的成員的信息的代碼在表單的 updatemember 方法中。要保存信息,發(fā)送一條 update 語(yǔ)句到服務(wù)器。表單中的更新語(yǔ)句如下:
update <table> set <column1> = <value1>,
<column2> = <value2>,
等等...
這相當(dāng)?shù)刂苯亓水?dāng),雖然構(gòu)造一個(gè)要發(fā)送到服務(wù)器的 update 語(yǔ)句看起來(lái)有些龐大。你知道表的字段名和表單上的控件中的值。你可以一個(gè)字段接一個(gè)字段地生成 update 的 set 部分。但是,象上面這樣書(shū)寫(xiě)可以使程序更清晰。你也可能不想浪費(fèi) sql server 的時(shí)間來(lái)更新沒(méi)有修改的字段。這里的代碼使用了 oldval() 函數(shù),使用游標(biāo)緩存使這樣做變?yōu)榭赡埽獙⒂螛?biāo)中各字段的值與原始的值進(jìn)行比較。只有當(dāng)它被修改了時(shí)才讓它成員 update 語(yǔ)句的一部分。另外,遠(yuǎn)程視圖自動(dòng)地這樣做了。
lcsql = ""
* 更新該成員到 member 表
if c_member.firstname <> oldval("c_member.firstname")
lcsql = lcsql + "firstname = '" + ;
alltrim(thisform.txtfirstname.value) + "', "
endif
if c_member.lastname <> oldval("c_member.lastname")
lcsql = lcsql + "lastname = '" +;
alltrim(thisform.txtlastname.value) + "', "
endif
<略去的代碼>
如果 member 表中沒(méi)有字段被修改,變量 lcsql 中將沒(méi)有內(nèi)容且不會(huì)保存信息到表中。如果有要保存的數(shù)據(jù),構(gòu)造剩下的 update 語(yǔ)句并發(fā)送到服務(wù)器。
if len(lcsql) > 0
* 添加 update,去掉最后一個(gè)逗號(hào),
* 添加一個(gè) where 子句
lcsql = "update member set " + ;
left(lcsql, len(lcsql) - 2) + ;
"where member_no = " + ;
alltrim(thisform.txtmemberid.value)
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代碼>
然后對(duì) adult 進(jìn)行與上面相同的處理。接著要做的是我們非常熟悉的事。如果一切正常提交事務(wù)處理否則回滾。
刪除一個(gè)成員
使用 sql pass-through 相對(duì)于遠(yuǎn)程視圖的一個(gè)優(yōu)點(diǎn)是,你增加了對(duì)發(fā)生什么和何時(shí)發(fā)生的控制。當(dāng)用戶單擊 delete 按鈕時(shí)運(yùn)行的代碼就是一個(gè)好的例子。
有很多理由可能使你不能刪除一個(gè)成員。如果成員有相關(guān)的 juveniles 或成員有未結(jié)清的借書(shū)時(shí)任何 delete 都會(huì)失敗。可以很容易地通過(guò)發(fā)送一個(gè) select 到服務(wù)器來(lái)檢查這一點(diǎn)。這里的代碼使用 sqlexec() 來(lái)檢查這兩個(gè)條件。如果兩者都為真,會(huì)顯示一個(gè)友好的信息且不會(huì)進(jìn)一步發(fā)生什么情況。
* 首先檢查是否有一個(gè)活動(dòng)的 juveniles
lcsql = "select member_no from juvenile " + ;
"where adult_member_no = " + ;
thisform.txtmemberid.value
if sqlexec(thisform.nhandle, lcsql) < 0
thisform.showerror
return
else
if reccount("sqlresult") <> 0
lcmessage = "該成員不能刪除. " + ;
"他是一個(gè)活動(dòng)的少年的成年。"
= messagebox(lcmessage, mb_iconinformation)
return
endif
endif
* 現(xiàn)在檢查成員還有活動(dòng)的借書(shū)
lcsql = "select member_no from loan " + ;
"where member_no = " + ;
thisform.txtmemberid.value
if sqlexec(thisform.nhandle, lcsql) < 0
thisform.showerror
return
else
if reccount("sqlresult") <> 0
lcmessage = "該成員不能刪除。" + ;
"他還有活動(dòng)的借書(shū)。"
= messagebox(lcmessage, mb_iconinformation)
return
endif
endif
如果還需要執(zhí)行額外的檢查,代碼可以放在以上代碼的后面。你可以完全控制要檢查的內(nèi)容和順序。如果所有的檢查都成功且成員可以刪除,則開(kāi)始一個(gè)事務(wù)處理。
在 member 表和 loanhist 及 reservation 表單定義了關(guān)系。每一次借書(shū)和還書(shū)都在 loanhist 表中有一條記錄。成員預(yù)約的每一本書(shū)在 reservation 表中有一條記錄。如果成員被刪除,需要?jiǎng)h除這兩個(gè)表中的相關(guān)信息。需要首先刪除它們,否則會(huì)違犯參照完整性。
* 刪除該成員的借書(shū)情況(loan history)記錄
lcsql = "delete loanhist where member_no = " + ;
alltrim(thisform.txtmemberid.value)
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代碼>
* 刪除該成員的借書(shū)預(yù)約(loan reservation)記錄
lcsql = "delete reservation where member_no = " + ;
alltrim(thisform.txtmemberid.value)
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代碼>
要?jiǎng)h除一個(gè)成人成員必須首先刪除 adult 表中的記錄然后才能刪除 member 表中的記錄。這也是事務(wù)處理的一部分,因此如何有任何錯(cuò)誤發(fā)生則回滾所有處理。
* 刪除成員
lcsql = "delete adult where member_no = " + ;
alltrim(thisform.txtmemberid.value)
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代碼>
lcsql = "delete member where member_no = " + ;
alltrim(thisform.txtmemberid.value)
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代碼>
如果所有刪除都沒(méi)有問(wèn)題則提交整個(gè)事務(wù)處理且成員被刪除。然后用戶會(huì)看到一個(gè)空的屏幕,因此添加一條空記錄到 c_member 并刷新表單顯示。
問(wèn)題
如何折衷地使用遠(yuǎn)程視圖和 sql pass-through?
這是更多的工作
顯然,使用 sql pass-through 比使用遠(yuǎn)程視圖需要做更多的工作。視圖為你做了大量的工作,維護(hù)與服務(wù)器的通信和傳遞 inserts,updates 和 deletes。視圖是易于設(shè)置和使用的。
你擁有更多的控制
你看到了兩種觀點(diǎn)的示例,使用遠(yuǎn)程視圖的一個(gè)問(wèn)題是在 visual foxpro 和后端間的通信上你只有較少的控制。對(duì)于多數(shù)據(jù)庫(kù)方案,這不是一個(gè)問(wèn)題。但是,在這里使用的方案中出現(xiàn)了問(wèn)題。問(wèn)題用每一個(gè)表使用一個(gè)視圖得到了緩解但任然存在。當(dāng)視圖不能給你以所需的數(shù)據(jù)輸入能力時(shí),問(wèn)題會(huì)更為突出。
當(dāng)你使用 sql pass-through 時(shí),你可以完全控制 visual foxpro 與后端的通信。你構(gòu)造 sql 語(yǔ)句并用 sqlexec() 來(lái)發(fā)送它們到服務(wù)器。如果需要執(zhí)行數(shù)據(jù)驗(yàn)證和檢查商業(yè)規(guī)則,你可以決定在什么時(shí)候和如何做。
錯(cuò)誤信息可以更友好
因?yàn)槟阍诳刂剖裁窗l(fā)生,并且你手動(dòng)的進(jìn)行數(shù)據(jù)驗(yàn)證,因此你可以控制錯(cuò)誤信息。在可以預(yù)見(jiàn)一些事發(fā)生時(shí),你可以截取 sql server 錯(cuò)誤信息并顯示給用戶一個(gè)可以理解的信息。由于不可預(yù)料的事件你也會(huì)遇到 sqlexec() 失敗的問(wèn)題,如網(wǎng)絡(luò)故障。對(duì)于這些,你可以決定是否分解信息或以原始方式顯示它們。
一方面它提供了較少的互用性
該方法的一個(gè)問(wèn)題是從某種程度上犧牲了互用性。通過(guò) sqlexec() 發(fā)送到后端的 sql 語(yǔ)句是用后端語(yǔ)言編寫(xiě)。在本示例中是 sql server。當(dāng)轉(zhuǎn)換到 oracle 時(shí)需要重寫(xiě)多少代碼?
雖然不同后端的基本的 select,insert,update 或 delete 格式?jīng)]有太大的不同。因此這里的示例可以很容易 轉(zhuǎn)移到其它后端。但是,重要的一點(diǎn)是取決于你使用的 sql 語(yǔ)句的復(fù)雜性,這可能限制了你交換后端的能力。當(dāng)然,如果應(yīng)用程序是為一種后端且只用一種后端,這將不是大的問(wèn)題。
另一方面它提供了更多的互用性
考慮如果你使用遠(yuǎn)程視圖而且你試著刪除一個(gè)沒(méi)有結(jié)清借書(shū)的成員會(huì)發(fā)生什么。在服務(wù)器上定義的參照完整性會(huì)防止刪除。但是,由 oracle 送回的錯(cuò)誤信息將與 sql server 送回的錯(cuò)誤信息不同。你可以分解錯(cuò)誤信息并將它們轉(zhuǎn)換到友好的格式,但是你將不得不分解不同的后端的信息。這就限制了你的互用性,因?yàn)槟悴坏貌粸椴煌姆?wù)器創(chuàng)建分解例程。
使用 sql pass-through 方法,你會(huì)發(fā)送一個(gè) select 語(yǔ)句到后端在 loan 表中搜索成員。如果 select 找到一條記錄則成員不能刪除。顯示給用戶的信息是相同的,而不論在 sqlexec() 中發(fā)送了什么。這種服務(wù)增了你的互用性,假定不同后端的 selects,inserts,updates 和 deletes 格式?jīng)]有多大變化,這是一種通情達(dá)理的假設(shè)。
新聞熱點(diǎn)
疑難解答
圖片精選