Visual Foxpro中的多用戶及數(shù)據(jù)緩沖問(wèn)題
2024-07-21 02:05:53
供稿:網(wǎng)友
本文來(lái)源于網(wǎng)頁(yè)設(shè)計(jì)愛(ài)好者web開發(fā)社區(qū)http://www.html.org.cn收集整理,歡迎訪問(wèn)。
主題:visual foxpro中的多用戶及數(shù)據(jù)緩沖問(wèn)題
紹
很多狐友(foxers)都是從dbase―foxbase―foxpro―vfp這樣一條路走過(guò)來(lái)的,如果說(shuō)從foxbase 到foxpro是一次飛躍,那么從foxpro到vfp就是一次升華。漫漫編程路上的兩次大變化都伴隨著升級(jí)的興奮與適應(yīng)的痛苦,慣性思維往往使我們?nèi)菀缀雎孕掳姹镜男聝?nèi)容。我們先來(lái)看看下面這個(gè)在表單中編輯記錄的例子:
在foxpro 2.x時(shí)代,我是這樣設(shè)計(jì)的:
1、 在屏幕上放置與表中字段對(duì)應(yīng)的文本框(text)控件,用來(lái)存放相應(yīng)的內(nèi)存變量(如m.cust_idt和m.name等);
2、 當(dāng)用戶將表的指針定位到某一特定記錄時(shí)(比如按了"下一個(gè)記錄"按鈕),就用scatter memvar 語(yǔ)句把該記錄的所有字段傳給相應(yīng)的內(nèi)存變量,再用show get來(lái)刷新屏幕上的文本框中顯示的值。在這一時(shí)刻,用戶是不能編輯這些變量的(此時(shí)文本框或者被設(shè)置為不可用(disabled),或者其when子句返回的是.f.值),因?yàn)橛脩籼幵冢g覽"狀態(tài)。
3、 當(dāng)用戶選擇了"編輯"按鈕時(shí),程序就鎖定該記錄(如果無(wú)法鎖定,則顯示提示信息),然后檢測(cè)每個(gè)字段值和相應(yīng)的內(nèi)存變量值(如果有不同的,說(shuō)明一定是有其它用戶在我們進(jìn)入編輯狀態(tài)后修改并保存了記錄,在這種情況下,要顯示提示信息),之后再用scatter memvar和show get語(yǔ)句刷新,這樣用戶就看到記錄中的當(dāng)前值了;
4、 如果所有字段和相應(yīng)的內(nèi)存變量完全匹配,就把變量所在的文本框控件都設(shè)置為可用(enabled)或都讓when子句返回.t.,以便讓用戶可以來(lái)編輯這些變量;
5、 當(dāng)用戶選擇了"保存"按鈕時(shí),根據(jù)一定的規(guī)則對(duì)輸入的數(shù)據(jù)進(jìn)行檢驗(yàn),然后用gatter memvar語(yǔ)句將內(nèi)存變量寫回到記錄中,并對(duì)記錄解鎖(unlock),再把所有的內(nèi)存變量所在文本框設(shè)置為不可用(disabled),或者讓其when子句返回.f.值,這時(shí),用戶又回到了"瀏覽"狀態(tài)。
請(qǐng)注意,在這一過(guò)程中我們沒(méi)有直接讀寫記錄,而是先將各字段值賦給同名內(nèi)存變量,再讓用戶編輯這些內(nèi)存變量,如果一切正常再把這些內(nèi)存變量寫回到記錄當(dāng)中。之所以用這種方法主要是為了保護(hù)表,如果不符合驗(yàn)證規(guī)則,就不允許數(shù)據(jù)回存到表中。另一點(diǎn)要注意的就是,當(dāng)用戶編輯記錄時(shí)要對(duì)記錄加鎖(lock),這樣就防止了其它用戶在同一時(shí)間編輯同一條記錄。但是,這種方法有一個(gè)很大的缺點(diǎn):假設(shè)一個(gè)用戶開始編輯某一記錄,在他按下"保存"按鈕前,記錄一直處于鎖定狀態(tài),如果此時(shí)該用戶有事暫時(shí)外出,如午餐,那么其它用戶就不能對(duì)該記錄進(jìn)行編輯了l。
當(dāng)然,你可以不在進(jìn)入"編輯"狀態(tài)時(shí)加鎖,而只在"保存"記錄之前加鎖,保存完后馬上解鎖,這樣可以使記錄被鎖定時(shí)間最短,以便其它用戶有充分的時(shí)間編輯該記錄。但是這也有缺點(diǎn),試想:如果用戶編輯了內(nèi)存變量,這后點(diǎn)擊"保存",可如果其它用戶在你點(diǎn)"保存"之前也編輯了這條記錄,并且還沒(méi)保存,這時(shí)會(huì)發(fā)生什么現(xiàn)象呢?你的保存要覆蓋他人的修改嗎?要放棄你的修改嗎?這些都是我們要在設(shè)計(jì)時(shí)認(rèn)真考慮的問(wèn)題。
我們?nèi)绱速M(fèi)盡心機(jī)地設(shè)計(jì),其目的都是為了保護(hù)數(shù)據(jù)。如果你寫的程序只是你一人使用,那設(shè)計(jì)起來(lái)可能會(huì)簡(jiǎn)單得多:你可以直接讀取記錄中的字段,比如你可以直接在屏幕中browse一個(gè)表,這樣你輸入的內(nèi)容就直接寫進(jìn)記錄了。但是,我們不能擔(dān)保那些最終用戶也象您一樣都清楚能輸入什么不能輸入什么,我們不得不在用戶與數(shù)據(jù)表之間建立一個(gè)"防火墻"來(lái)保護(hù)數(shù)據(jù)。在foxpro 2.x中創(chuàng)建這樣的"防火墻"要寫一大堆的代碼!
值得我們高興的是,vfp提供了內(nèi)建的"防火墻"機(jī)制,它有兩方面的作用:一是可直接讀取記錄,二是只允許通過(guò)所有檢驗(yàn)規(guī)則的數(shù)據(jù)被寫回。這一機(jī)制就是緩沖。
緩沖
在剛才我們講到的例子中,是通過(guò)內(nèi)存變量存貯記錄內(nèi)容,這種方法可以被認(rèn)為是手工建立了一個(gè)數(shù)據(jù)緩沖器,通過(guò)使用scatter memvar把數(shù)據(jù)從記錄中傳送到"緩沖"中,再用gather memvar從"緩沖"中傳回到記錄。
vfp不僅能自動(dòng)進(jìn)行單條記錄的緩沖(稱為行緩沖或記錄緩沖),而且還支持另一種類型的緩沖,即表緩沖,表緩沖可通過(guò)緩沖器存取多條記錄。行緩沖一般用于一次存取一個(gè)記錄時(shí),這種機(jī)制普遍應(yīng)用于數(shù)據(jù)錄入,就象前文提到的那樣:用戶可以在表單中顯示或編輯單條記錄。表緩沖則適用于一次更新多條記錄,比如一張訂貨單的明細(xì)錄入屏幕,通過(guò)對(duì)物品明細(xì)表使用表緩沖,可以允許用戶編輯多條明細(xì)記錄,最后一次性地將所有明細(xì)記錄保存或放棄。
除了兩種緩沖機(jī)制,vfp還有兩種鎖定機(jī)制。前文講述的那種在foxpro 2.x中的加鎖方式被稱為保守式鎖定法(或悲觀鎖定法)――當(dāng)用戶選擇"編輯"時(shí)加鎖,直到用戶選擇了"保存"后再解鎖。這種加鎖機(jī)制確保了當(dāng)本用戶修改記錄時(shí)其它用戶都不能修改該記錄,但這樣做有利有弊,視具體情況而定。前文所講的另一種加鎖方式叫做開放式加鎖法(或樂(lè)觀鎖定法)――只有在寫回記錄時(shí)才鎖定該記錄,然后馬上解鎖。這種加鎖機(jī)制雖然使記錄最大時(shí)間內(nèi)可給別的用戶使用,但我們必須處理當(dāng)兩個(gè)用戶同時(shí)編輯記錄時(shí)所造成的沖突。正如我們下面將要看到的,這對(duì)于vfp來(lái)說(shuō)真是太簡(jiǎn)單了,在vfp中大多數(shù)的情況下都選用開放式緩沖機(jī)制。
因?yàn)樵趘fp中記錄可以被自動(dòng)緩沖,所以就不必再用"人工緩沖"機(jī)制了,換句話說(shuō),現(xiàn)在我們可以直接讀記錄中的字段而不必關(guān)心為每個(gè)字段設(shè)內(nèi)存變量。要保存修改,我們只要簡(jiǎn)單地告訴vfp將緩沖器中的內(nèi)容寫到表中即可,若是取消修改,告訴vfp不寫入即可。過(guò)會(huì)兒我們將看到這是如何操作的。
當(dāng)打開一個(gè)表時(shí),vfp創(chuàng)建一個(gè)"臨時(shí)表"(cursor)作為緩沖器,這個(gè)"臨時(shí)表"用來(lái)定義表的屬性。對(duì)于本地表來(lái)說(shuō),"臨時(shí)表"的唯一屬性是用來(lái)定義緩沖方式的的,視圖和遠(yuǎn)程表還有一些本文討論范圍之外的其它屬性,這些屬性的值用cursorsetprop()函數(shù)設(shè)置,用cursorgetprop()取得。我們過(guò)會(huì)兒將看到如何使用這些函數(shù)。
當(dāng)追加記錄時(shí),表緩沖有一個(gè)有趣的特性:隨著記錄添加到緩部器中,它們被賦予一個(gè)負(fù)記錄號(hào),第一個(gè)加入的記錄,recno()返回值為-1,第二個(gè)返回值為-2,依此類推。你可以用一個(gè)帶負(fù)數(shù)的go命令在緩沖中定位到追加的記錄上。這在處理記錄號(hào)時(shí)很有用,比如為了確認(rèn)變量inrecno是否為一個(gè)可用的記錄號(hào),在緩沖狀態(tài)下,要檢測(cè)between(inrecno,1,reccount()) or inrecno<0 而不能只用between(inrecno,1,reccount())。
使用緩沖
缺省情況下緩沖器是關(guān)閉的,這種情況下在更新表時(shí)vfp和foxpro 2.x是一樣的,要使用緩沖你必需將它打開。緩沖適用于自由表及數(shù)據(jù)庫(kù)中的表。要用緩沖還要set multilocks on,因?yàn)閙ultilocks缺省值是off,如果你忘了設(shè)置其值為on,會(huì)出現(xiàn)錯(cuò)誤信息。你可以把multilocks=on加入到config.fpw文件中,或是用"工具"欄下的"選項(xiàng)"功能保存其值為on。
我們通過(guò)cursorsetprop(‘buffering’,<n>,<alias>)來(lái)定義緩沖方式。如果是對(duì)當(dāng)前表設(shè)置緩沖,不必定義<alias>,根據(jù)你想要的緩沖及鎖定方式<n>為下列值:
緩沖及鎖定法
<n>值
無(wú)緩沖
1
保守式行緩沖
2
開放式行緩沖
3
保守式表緩沖
4
開放式表緩沖
5
例如,要將當(dāng)前表設(shè)置為開放式行緩沖,用cursorsetprop(‘buffering’,3),要取得當(dāng)前正打開的表的緩沖方式,用cursorgetprop(‘buffering’)。
要設(shè)置一個(gè)表單的緩沖方式,你可以在表單的load事件中用cursorsetprop()定義所有用到的表,但最好的方法是直接設(shè)置表單的buffermode屬性來(lái)決定是開放式還是保守式(缺省值為"無(wú)"),這樣設(shè)置后,該表單就會(huì)自動(dòng)對(duì)綁定到網(wǎng)格(grid)中的表使用表緩沖,對(duì)其它表則使用行緩沖。如果你的表單使用了數(shù)據(jù)環(huán)境,你可以對(duì)某一個(gè)表的buffermodeoverride屬性按你的意圖設(shè)置緩沖,以取代表單的buffermode屬性。
如果有用戶正在修改緩沖記錄中的數(shù)據(jù)(此時(shí)用戶處在"編輯"狀態(tài)),你不僅可以取得他們輸入到每個(gè)字段中的值,還能取得每個(gè)字段的初始值和當(dāng)前值(此值為磁盤中的實(shí)際值),為此vfp提供了oldval()和curval()函數(shù)。
要得到:
使用:
用戶輸入值(緩沖的數(shù)據(jù))
<fieldname> or <alias.fieldname>
用戶未做任何改動(dòng)之前的值
oldval(‘<fieldname>’)
當(dāng)前記錄中的值
curval(‘<fieldname>’)
注: curval()和oldval()僅用于開放式緩沖。
你可能搞不懂curval()返回值和oldval()返回值有什么不同,如果是在單用戶程序中,二者之間是沒(méi)什么區(qū)別,但是如果是在網(wǎng)絡(luò)中,在開放式鎖定下,很可能在本用戶編輯一條記錄時(shí),另一個(gè)用戶也編輯同一條記錄,并在本用戶"保存"之前進(jìn)行了"保存",下面是一個(gè)例子:
鄭某將contacts.dbf指針定位到第2條記錄,并點(diǎn)擊了"編輯"按鈕:
字段
緩沖值
初始值oldval()
當(dāng)前值curval()
姓名
李達(dá)
李達(dá)
李達(dá)
公司名稱
狐友技術(shù)開發(fā)公司
狐友技術(shù)開發(fā)公司
狐友技術(shù)開發(fā)公司
然后,鄭某將公司名稱改為"狐友俱樂(lè)部",但還沒(méi)有保存記錄:
字段
緩沖值
初始值oldval()
當(dāng)前值curval()
姓名
李達(dá)
李達(dá)
李達(dá)
公司名稱
狐友俱樂(lè)部
狐友技術(shù)開發(fā)公司
狐友技術(shù)開發(fā)公司
就在此時(shí),于某也將contacts.dbf指針定位到第2條記錄,并點(diǎn)擊了"編輯"按鈕,他改變了公司名稱為"獵狐者俱樂(lè)部",并保存。此時(shí)在鄭某的機(jī)器上會(huì)看到如下結(jié)果:
字段
緩沖值
初始值oldval()
當(dāng)前值curval()
姓名
李達(dá)
李達(dá)
李達(dá)
公司名稱
狐友俱樂(lè)部
狐友技術(shù)開發(fā)公司
獵狐者俱樂(lè)部
注意:在上表中contacts.公司名稱、oldval(‘公司名稱’)以及curval(‘公司名稱’)將返回不同的值。訪問(wèn)記錄中各字段的初始值、緩沖值和當(dāng)前值,你可以:
l 通過(guò)比較緩沖值和初始值來(lái)確定哪些字段被用戶修改了;
l 通過(guò)比較初始值和當(dāng)前值來(lái)檢測(cè)在開始編輯后,網(wǎng)絡(luò)中是否有其它用戶修改了同一條記錄。 如果你不關(guān)心初始值和當(dāng)前值,而只是希望檢測(cè)到某個(gè)字段中的內(nèi)容是否被修改過(guò),可以用getfldstate()函數(shù)。這個(gè)函數(shù)返回一個(gè)數(shù)值,指出當(dāng)前記錄是否被做了修改。getfldstate()按以下格式調(diào)用:
getfldstate( <fieldname> | <fieldnumber> [ , <alias> | <workarea> ] )
返回值及其意義如下表所示:
返回值
意義
1
沒(méi)改變
2
字段被編輯或者記錄的刪除標(biāo)記被改變
3
添加了一條新記錄但沒(méi)編輯字段,以及記錄的刪除記錄未改變
4
添加了一條新記錄并編輯了字段,或者記錄的刪除標(biāo)記被改變
記錄的刪除標(biāo)記被改變,是指刪除記錄或恢復(fù)(recall)記錄。值得注意的是:對(duì)記錄刪除后又馬上進(jìn)行了恢復(fù),盡管對(duì)記錄來(lái)說(shuō)沒(méi)影響,但是其刪除標(biāo)記被改變過(guò),因此,getfldstate()函數(shù)會(huì)返回2或4。
如果你沒(méi)有定義別名或工作區(qū),getfldstate()將對(duì)當(dāng)前打開的表進(jìn)行操作。將<fieldnumber>定義為0,該函數(shù)返回當(dāng)前記錄的添加及刪除狀態(tài),如果定義為-1,將返回一個(gè)字符串,在這個(gè)字符串中,第一個(gè)數(shù)字反映整個(gè)表的狀態(tài),以后每個(gè)數(shù)字返映的是各字段的狀態(tài)。
以我們前面講到的情況為例,在鄭某編輯第2條記錄時(shí),getfldstate(-1)將返回"112",第一個(gè)數(shù)字"1"說(shuō)明記錄沒(méi)有添加或刪除,第二個(gè)數(shù)字"1"說(shuō)明第一個(gè)字段(姓名)沒(méi)有改變,第三個(gè)數(shù)字"2"說(shuō)明第二個(gè)字段(公司名稱)內(nèi)容改變了。
緩沖記錄的寫回
我們還繼續(xù)剛才的例子。現(xiàn)在假設(shè)鄭某點(diǎn)擊了"保存"按鈕,我們應(yīng)該怎樣將緩沖中的數(shù)據(jù)寫到記錄中(更新表)呢?對(duì)于行緩沖來(lái)說(shuō),當(dāng)你移動(dòng)記錄指針或調(diào)用tableupdate()函數(shù)時(shí),表就會(huì)被更新。對(duì)于表緩沖來(lái)說(shuō),移動(dòng)記錄指針并不會(huì)引起表的更新(因?yàn)樗嵌嘤涗洷煌瑫r(shí)緩沖),所以通常情況下只能調(diào)用tableupdate()函數(shù)來(lái)更新表。對(duì)于行緩沖最好也用tableupdate()函數(shù),因?yàn)檫@樣更好地控制程序的去向。
如果緩沖器中的內(nèi)容被正確地寫入到記錄中,tableupdate()返回.t.值,如果記錄緩沖沒(méi)有改變(用戶沒(méi)有編輯任何字段、添加記錄或改變記錄的刪除狀態(tài)),此時(shí),tableupdate()也返回.t.,盡管實(shí)際上什么也沒(méi)有做。
tableupdate()可以帶幾個(gè)參數(shù):
tableupdate( <allrows>,<forced>,<alias>|<workarea> )
第一個(gè)參數(shù)指明哪些記錄被更新:設(shè)為.f.,則只更新當(dāng)前記錄,若為.t.,則更新所有記錄(僅影響表緩沖)。如果第二個(gè)參數(shù)是.t.,那么其它用戶的任何修改將被當(dāng)前用戶的修改所覆蓋。如果沒(méi)定義第三個(gè)參數(shù),tableupdate()將更新當(dāng)前表。
怎樣取消用戶所做的修改呢?對(duì)于用內(nèi)存變量的方法,可以再次用scatter memevar 語(yǔ)句從磁盤上的數(shù)據(jù)恢復(fù)到內(nèi)存變量中,而對(duì)于緩沖來(lái)說(shuō),用tablerevert()函數(shù)即可達(dá)到同樣功能。
錯(cuò)誤處理
我們繼續(xù)"鄭某和于某"的例子,當(dāng)鄭某點(diǎn)擊"保存"按鈕后,代碼將執(zhí)行tableupdate()函數(shù)以把緩沖中的數(shù)據(jù)寫入記錄。請(qǐng)記住,在鄭某編輯記錄時(shí)于某已經(jīng)修改了同一條記錄并做了保存。當(dāng)鄭某點(diǎn)擊"保存"時(shí),tableupdate() 將返回.f.,說(shuō)明它不能將緩沖寫入記錄中,為什么會(huì)是這樣呢?
vfp在以下幾種情況下無(wú)法將緩沖寫入記錄:
l 當(dāng)一個(gè)用戶編輯記錄時(shí),其它用戶修改并保存了該記錄(正如我們例子中的那種情況)。vfp自動(dòng)對(duì)每個(gè)字段的oldval()值和curval()值進(jìn)行比較,如果檢測(cè)到任何不同,就會(huì)產(chǎn)生沖突。
l 用戶輸入了重復(fù)的主索引或候選索引值。
l 違背了某個(gè)字段或表的驗(yàn)證規(guī)則,或者不支持null的字段出現(xiàn)了null值。
l 某個(gè)觸發(fā)(trigger)失敗。
l 其它用戶鎖定了該記錄。
l 其它用戶刪除了該記錄。
當(dāng)tableupdate()失敗時(shí),我們必須決定下一步做什么,而且,如果你的程序在編輯記錄時(shí)允許用戶點(diǎn)擊"下一個(gè)"或"上一個(gè)"按鈕,而這兩個(gè)按鈕中又沒(méi)有調(diào)用tableupdate()的話,你必須得處理在自動(dòng)保存時(shí)將會(huì)發(fā)生的錯(cuò)誤。在這兩種情況下,將程序指定到適當(dāng)?shù)奈恢镁褪清e(cuò)誤陷阱處理程序。
visual foxpro中的多用戶及數(shù)據(jù)緩沖問(wèn)題(下)
--------------------------------------------------------------------------------
2000-10-6 17:07:00
在vfp中錯(cuò)誤處理已經(jīng)得到改進(jìn)。以前處理錯(cuò)誤陷阱的方法(你仍然可以在vfp中繼續(xù)使用這些方法)是當(dāng)錯(cuò)誤發(fā)生時(shí)用on error命令來(lái)決定要執(zhí)行的程序,典型的錯(cuò)誤處理程序是查看error()和message()來(lái)確定發(fā)生了什么錯(cuò)誤,然后采取相應(yīng)的動(dòng)作。
現(xiàn)在vfp提供了一種自動(dòng)的錯(cuò)誤處理機(jī)制:就是error方法。如果定義了一個(gè)控件或表單中的error方法,當(dāng)錯(cuò)誤發(fā)生時(shí)它就被自動(dòng)執(zhí)行。aerror()是vfp的一個(gè)新增函數(shù),通過(guò)傳遞一個(gè)參數(shù),該函數(shù)可以創(chuàng)建或更新一個(gè)含有以下元素的數(shù)組
元素
類型
描述
1
數(shù)字
錯(cuò)誤號(hào)(與error()相同)
2
字符
錯(cuò)誤信息(與message()相同)
3
字符
如果有一個(gè)錯(cuò)誤信息參數(shù)(與sys(2018)相同),則返回之(例如:一個(gè)字段名),無(wú),則返回.null.
4
數(shù)字或字符
發(fā)生錯(cuò)誤的工作區(qū)。如果沒(méi)有,則返回.null.
5
數(shù)字或字符
如果一個(gè)觸發(fā)器失敗,返回觸發(fā)器號(hào)(插入為1,更新為2,刪除為3),如果沒(méi)有則返回.null.
6
數(shù)字或字符
.null.(應(yīng)用于ole和odbc錯(cuò)誤)
7
數(shù)字
.null.(應(yīng)用于ole錯(cuò)誤)
例如:aerror(iaerror)會(huì)創(chuàng)建或更新一個(gè)稱為iaerror的數(shù)組。
以下是vfp在將緩沖寫入表時(shí)可能發(fā)生的一些錯(cuò)誤:
錯(cuò)誤號(hào)#
錯(cuò)誤信息
說(shuō)明
109
記錄正由其它用戶使用
1539
觸發(fā)器失敗
檢測(cè)數(shù)組的第5個(gè)元素可以確定是哪個(gè)觸發(fā)器失敗了
1581
字段不接受空值(null)
檢測(cè)數(shù)組的第3個(gè)元素可以確定是哪個(gè)字段引起的錯(cuò)誤
1582
違反了字段的驗(yàn)證規(guī)則
檢測(cè)數(shù)組的第3個(gè)元素可以確定是哪個(gè)字段引起的錯(cuò)誤
1583
違反了記錄的驗(yàn)證規(guī)則
1585
記錄已被其它用戶修改
1884
違反了索引的唯一性
檢測(cè)數(shù)組的第3個(gè)元素可以確定是哪個(gè)索引標(biāo)記引起的錯(cuò)誤
對(duì)于以上這些錯(cuò)誤,大部分可以直接處理:提示用戶問(wèn)題所在,然后讓用戶在"編輯"狀態(tài)下改正錯(cuò)誤或是取消操作。對(duì)于#1585錯(cuò)誤(記錄已被其它用戶修改),有以下幾種處理錯(cuò)誤的方法:
l 可以提示當(dāng)前用戶有別人修改了該記錄,然后用tablerevert()取消當(dāng)前用戶的編輯內(nèi)容。我想多數(shù)人對(duì)這種方法會(huì)不高興的。
l 可以用tableupdate(.f. , .t.)來(lái)強(qiáng)行更新記錄,使當(dāng)前用戶的修改覆蓋其它用戶修改。這樣做,當(dāng)前用戶自然是高興的,但其它用戶可能就要不滿了。:(
l 復(fù)制一個(gè)相同的表單(在vfp中對(duì)同一表單創(chuàng)建多個(gè)實(shí)例是非常容易的),在上面顯示出其它用戶對(duì)該記錄的修改,這樣,當(dāng)前用戶就可以決定是保存還是不保存其它用戶的修改,也就是說(shuō),可以用tableupdate(.f. , .t.)強(qiáng)行更新或是用tablerevert()來(lái)取消編輯。
有更好的方案來(lái)檢測(cè)我們是否遇到了"真正的"沖突,所謂"真正的"沖突,是指兩個(gè)用戶在同一時(shí)間修改了同一個(gè)字段。如果他們修改的是同一記錄的兩個(gè)不同字段,我們可以只更新當(dāng)前用戶修改的字段,而保留另一個(gè)用戶修改的字段,使之不受影響。例如,在一個(gè)訂單處理系統(tǒng)中,一個(gè)用戶修改了產(chǎn)品介紹,而同時(shí)另一個(gè)用戶在對(duì)該產(chǎn)品下訂單,正在輸入數(shù)量,這兩個(gè)修改相互獨(dú)立,并沒(méi)有沖突,這時(shí)我們不要一次更新整條記錄,而是只更新自己修改的那個(gè)字段,如此一來(lái),兩個(gè)用戶都會(huì)感到滿意。
以下是實(shí)現(xiàn)這一想法的思路:
l 查找oldval()與curval()不同的字段,如果有,說(shuō)明該字段已被其它用戶編輯過(guò),如果該字段的緩沖值與oldval()相同,說(shuō)明當(dāng)前用戶并沒(méi)有修改該字段。這種情況下,我們可以將curval()中的值先傳給緩沖值,再更新,這樣就可以避免緩沖值覆蓋新值了。
l 查找緩沖值與oldval()不同的字段,這些字段是當(dāng)前用戶修改過(guò)的字段,如果oldval()又等于curval(),說(shuō)明其它用戶沒(méi)有改動(dòng)過(guò)該字段,這種情況下,我們可以放心地覆蓋掉它。
l 如果我們找到的字段,其緩沖值與oldval()不同,但與curval()相同,這說(shuō)明兩個(gè)用戶對(duì)同一字段做了相同的修改。這種情況看起來(lái)好象不大可能,其實(shí)是會(huì)發(fā)生的。比如有人發(fā)來(lái)了一個(gè)公司的地址變動(dòng)信息,而恰恰有兩個(gè)用戶同時(shí)決定據(jù)此來(lái)更新記錄中的公司地址。因?yàn)樗麄兯龅男薷膬?nèi)容是相同的,我們可以覆蓋另一個(gè)即可。但是,如果是以同一個(gè)數(shù)量更新一個(gè)量值的話(比如兩人同時(shí)下訂單,且輸入了相同的數(shù)量),你就不能簡(jiǎn)單地覆蓋字段,應(yīng)該將此看作為"真正的"沖突。
l 如果發(fā)現(xiàn)一個(gè)字段的緩沖值既不同于oldval()也不同于curval(),而且oldval()與curval()也各不相同,這說(shuō)明兩個(gè)用戶都修改了同一個(gè)字段,且值不相同。這種情況才是我們遇到的真正的沖突,我們不得不決定怎樣處理這一沖突。
在手工輸入存貨數(shù)量或帳目余額時(shí),有一種可能就是其它用戶輸入的值對(duì)緩沖值會(huì)產(chǎn)生影響。比如,如果檢測(cè)到oldval()是10, curval()是20,說(shuō)明其它用戶將數(shù)額已增加了10;如果現(xiàn)在緩沖值為5,說(shuō)明當(dāng)前用戶要將數(shù)額在原基礎(chǔ)(10)上減少5,因此新的緩沖值就應(yīng)該是value+ oldval()- curval(),即等于15,應(yīng)該用此數(shù)來(lái)更新字段。
對(duì)于日期型字段的沖突,就要考慮商業(yè)規(guī)則或按實(shí)際情況處理。例如,在"病人預(yù)約時(shí)間"程序中,有一個(gè)字段保存著病人下次預(yù)約醫(yī)生的時(shí)間,如果該字段兩個(gè)日期發(fā)生沖突的話,那么,靠近當(dāng)前日期的那個(gè)日期很可能是正確的。當(dāng)然,若是其中一個(gè)預(yù)約日期比當(dāng)前日期還要早的話(已經(jīng)過(guò)期),那就應(yīng)該取靠后的那個(gè)日期了。
其它類型的字段,特別是字符型和備注型字段,通常情況下如果不詢問(wèn)用戶以決定是覆蓋其它用戶的修改還是取消自己的修改的話,則沖突不好解決。只有當(dāng)用戶在屏幕上看到其它用戶到底做了什么修改后,他才能做出正確地判斷。
以下是解決沖突的一些代碼(這些代碼假設(shè)已檢測(cè)到錯(cuò)誤碼為#1585,即記錄已被其它用戶修改過(guò)。)
* 檢測(cè)每一個(gè)字段,看哪個(gè)發(fā)生了沖突。
llconflict = .f.
for lni = 1 to fcount()
lcfield = field(lni)
llotheruser = oldval(lcfield) <> curval(lcfield)
llthisuser = evaluate(lcfield) <> oldval(lcfield)
llsamechange = evaluate(lcfield) == curval(lcfield)
do case
* 其它用戶編輯了該字段,而當(dāng)前用戶沒(méi)編輯,所以直接用新值即可。
case llotheruser and not llthisuser
replace (lcfield) with curval(lcfield)
* 其它用戶沒(méi)有編輯該字段,或者二者做了相同的修改,因此我們無(wú)需做任何處理。
case not llotheruser or llsamechange
* 兩個(gè)用戶以不同的值修改了該字段。
otherwise
llconflict = .t.
endcase
next lni
* 如果發(fā)生了沖突,處理之!
if llconflict
lnchoice = messagebox('another user also changed this ' + ;
'record. do you want to overwrite their changes (yes), ' + ;
'not overwrite but see their changes (no), or cancel ' + ;
'your changes (cancel)?', 3 + 16, 'problem saving record!')
do case
* 覆蓋其它用戶的修改。
case lnchoice = 6
= tableupdate(.f., .t.)
* 通過(guò)產(chǎn)生一個(gè)表單實(shí)例來(lái)查看其它用戶的修改內(nèi)容。
case lnchoice = 7
do form myform name oname
* 取消當(dāng)前用戶的修改。
otherwise
= tablerevert()
endcase
* 如果沒(méi)有發(fā)生沖突,則強(qiáng)行更新。
else
= tableupdate(.f., .t.)
endif llconflict
表緩沖的寫入
我們前面已經(jīng)講到,可以用tableupdate(.t.)將表緩沖中的所有記錄一次寫入磁盤。與行緩沖一樣,如果其它用戶修改了表(或是其它什么出錯(cuò)原因)而不能正確更新表,tableupdate(.t.)將返回.f.值。
前文所講的錯(cuò)誤處理程序在行緩沖模式下運(yùn)行良好,因?yàn)槲覀冊(cè)谀骋粫r(shí)刻只關(guān)心單條記錄。但對(duì)于表緩沖來(lái)說(shuō),我們不得不考慮每一條記錄,因?yàn)樵诰彌_區(qū)中可能既有修改過(guò)的記錄,也有未修改過(guò)的記錄,我們?cè)鯓又赖降赘履臈l記錄呢?如果用tableupdate(.t.)失敗(返回.f.),情況就變得更加復(fù)雜化:我們不知道錯(cuò)在哪條記錄上!而且有些記錄可能被做過(guò)"保存",所以還不止一條記錄會(huì)發(fā)生沖突呢。請(qǐng)不要著急:),vfp新增函數(shù)getnextmodified()可以精確地告訴我們想知道的信息:該函數(shù)返回下一個(gè)被修改記錄的記錄號(hào)。如果返回值為0,說(shuō)明在緩沖區(qū)中沒(méi)有被修改過(guò)的記錄。這個(gè)函數(shù)接收兩個(gè)參數(shù):第一個(gè)參數(shù)是一個(gè)記錄號(hào),正是從這個(gè)記錄號(hào)開始向下查找下一個(gè)被修改的記錄;第二個(gè)參數(shù)是查找的工作區(qū)別名。最被,你應(yīng)該將0傳給第一個(gè)參數(shù),這樣getnextmodified()就會(huì)找到第一個(gè)被修改的記錄,若要繼續(xù)找下一個(gè)被修改的記錄,只要將當(dāng)前記錄的記錄號(hào)傳給第一個(gè)參數(shù)即可。
下面是在剛才處理沖突的程序基礎(chǔ)上改進(jìn)后的代碼,它用來(lái)處理表緩沖更新失敗時(shí)的操作。
* 先找到第一個(gè)被修改過(guò)的記錄。
lnchanged = getnextmodified(0)
do while lnchanged <> 0
* 移動(dòng)記錄指針并嘗試鎖定它。
go lnchanged
if rlock()
* 檢測(cè)每一個(gè)字段,看哪個(gè)發(fā)生了沖突。
llconflict = .f.
for lni = 1 to fcount()
lcfield = field(lni)
llotheruser = oldval(lcfield) <> curval(lcfield)
llthisuser = evaluate(lcfield) <> oldval(lcfield)
llsamechange = evaluate(lcfield) == curval(lcfield)
do case
* 其它用戶編輯了該字段,而當(dāng)前用戶沒(méi)編輯,所以直接用新值即可。
case llotheruser and not llthisuser
replace (lcfield) with curval(lcfield)
* 其它用戶沒(méi)有編輯該字段,或者二者做了相同的修改,因此我們無(wú)需做任何處理。
case not llotheruser or llsamechange
* 兩個(gè)用戶以不同的值修改了該字段。
otherwise
llconflict = .t.
endcase
next lni
* 如果發(fā)生了字段沖突,我們可以在此處理它,與行緩沖不同的是,我們也可以現(xiàn)在不處理,因?yàn)橐院笏杏涗泴⒈粚懭耄綍r(shí)會(huì)處理的。
if llconflict
lnchoice = messagebox('another user also changed ' + ;
'record ' + ltrim(str(lnchanged)) + '. do you want to ' + ;
'overwrite their changes (yes), not overwrite but see ' + ;
'their changes (no), or cancel your changes (cancel)?', 3 + 16, ;
'problem saving record!')
do case
* 如果選擇了覆蓋其它用戶的修改,在此可以不做處理,因?yàn)橐院髮⒁淮涡愿隆?
case lnchoice = 6
*通過(guò)產(chǎn)生一個(gè)表單實(shí)例來(lái)查看其它用戶的修改內(nèi)容。
&nb
轉(zhuǎn)載!