在Delphi中如何維護COM+的狀態訊息
劉曉明(cipherliu)
問題是這樣開始的:我需要寫一個COM+,用來連接不同的資料庫。有的朋友可能會說,應該為每個資料庫建立一個COM+,但是在我的系統裡不能這樣做。我們在做一個教育輔助系統,使用者是學校(當然包括學校裡的老師、學生、家長),我們為每個學校建立一個資料庫,這些資料庫的結構是相同。當然我們還有管理資料庫,用來協調各資料庫的關係。每增加一個學校用戶,我們就會啟動一個新的資料庫給客戶使用,也就是說,我們的資料庫的數量是不斷增加的,而我們的客戶端只有一個,我們不會為每個學校開發不同的客戶端,我們的COM+也只有一組,而不是為每個資料庫開發一組。所以我必須在COM+中根據使用者的身份讓它連接不同的資料庫。
很顯然,這個COM+應當提供一個方法,讓其調用者(可以是客戶端應用程序,也可以是其它的中間件)去選擇連接的數據庫,在實際中我們是根據用戶的ID在管理庫中查到它的資料庫名,然後連接用戶資料庫,在這裡,為了簡化問題,我們認為呼叫者已經知道了資料庫的名字,而直接要求呼叫這個資料庫。
在COM+的類別中增加一個私有成員DBName:string,用來保存要連接的資料庫名稱。也應該提供這樣一個方法來設定它的值,我開始是這樣寫的
PRocedure TmtsDBConn.ConnectTo(sDBName:string);
begin
try
DBName:=sDBName;
SetComplete;
Except
SetAbort;
end;
end;
然後在其中放入ADOConnection,ADODataSet,和DataSetProvider控件,分別取名為adoc,adods,dsp。設定好它們之間的連接關係,把adoc的連接字串設為連接資料庫“DB1”,這是預設值,然後在adoc的BeforeConnect事件中:
adoc.ConnectionString:=ConnectStringA+'Initial Catalog='+DBName+';'+ConnectStringC;
這裡的ConnectStringA和ConnectStringC是為了動態建立連接字串,預先設定好的字串常數,如下:
const
ConnectStringA='Provider=SQLOLEDB.1;PassWord=2003;Persist Security Info=True;User ID=sa;';
ConnectStringB='Initial Catalog=DB1;';
ConnectStringC='Data Source=server3;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=LXM;Use Encryption for Data=False;Tag with column collation when possible=False';
編譯、安裝這個COM+。然後編寫客戶端程式調用它。
在客戶端程式中放一個DCOMConnection,連接到上面寫COM+伺服器,再放一個ClientDataSet,設定它的RemoteServer和Provider屬性,然後在它的CommandText中寫入SQL語句。然後,放入DataSource控制項和DBGrid控制項,建立好它們之間的連接關係。最後放一個按鈕,在它的Click事件中:
Dcomconnection1.Connected:=true;
Dcomconnection1.AppServer.connect('DB2');
ClientDataset1.Active:=true;
Dcomconnection1.Connected:=false;
這段程式碼是想測試一下,能不能存取到DB2資料庫的資料。可是結果是,點下按鈕時,總是報錯,這是什麼原因呢?
回到COM+的工程,調試它,在ConnectTo和adocBeforeConnect中設定斷點,發現程式執行到
DBName:=sDBName;
時,確實已經把DBName的值設為」DB2」了,但在執行
adoc.ConnectionString:=ConnectStringA+'Initial Catalog='+DBName+';'+ConnectStringC;
時,DBName又成了空字串,所以出錯了。
為什麼DBName的值會丟掉呢?原來在是因為在ConnectTo中,呼叫了SetComplete方法,SetComplete方法認為這個COM+已經完成了任務,會釋放這個COM+對象,所以連接資料庫時,又建立了一個新的COM+,它的DBName當然是空值了。
找到了原因,把SetComplete改成EnableCommit; 編譯,再執行客戶端,終於運作成功,取回了DB2資料庫中的資料。
可是在客戶端程式中,放入另一個ClientDataSet,在開啟ClientDataSet1之後,開啟ClientDataSet2,想繼續存取DB2中的數據,又報錯了。把程式改成
Dcomconnection1.AppServer.connect('DB2');
ClientDataset1.Active:=true;
ClientDataset1.Active:=false;
ClientDataset1.Active:=true;
即使只使用一個ClientDataSet,在它關閉之後,再打開時,還是會出錯。
但如果客戶端寫成
Dcomconnection1.AppServer.connect('DB2');
ClientDataset1.Active:=true;
Dcomconnection1.AppServer.connect('DB2');
ClientDataset2.Active:=true;
可以執行成功。但這似乎很不好看,COM+為什麼會在連接完資料庫後又把自己釋放呢?
原來,TmtsDataModule有一個AutoComplete屬性,預設值是true,所以連接完資料庫之後,它還是會把自己釋放出來。
把AutoComlete設為false後,還是出錯,在COM+的OnActivate事件中追蹤發現,當它被啟動時,AutoComplete屬性被自動設為ture了,所以在它第一次連接資料庫後,依然會把自己釋放。
在COM+的OnOnActivate事件中,寫上:
AutoComplete:=false;
客戶端一次連接,多次存取資料庫也沒有問題了。
但這樣一來,COM+就不會自動釋放了,需要在COM+中增加一個方法,在這個方法中SetComplete,然後在客戶端用完COM+後,呼叫這個方法來釋放COM+。
經過以上的摸索,得出以下的結論:在COM+中,如果要保持狀態信息,需要做一些工作,因為COM+默認是無狀態的,它每次被客戶端調用後,都會判斷是不是應該釋放自己,如果我們不想它釋放,就要人工加以乾預,最後我們還要人工去釋放它。