程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> DELPHI多層分布式開發(2)

DELPHI多層分布式開發(2)

編輯:Delphi

客戶端實現SQL查詢

由於在客戶端不存在TQuery控件,似乎客戶服務器模式是無法做SQL查詢的。 但是,Delphi很好的解決了這個問題。事實上,只要客戶端連接上服務端應用程 序,客戶端的TClientDataSet就包含了一個名字為Provider的屬性,對應到服務器端DataSetProvider的所有默認屬性和方法,其中DataSetProvider有一個 Options屬性,只要讓其中的poAllowCommandText=true,那麼, DataSetProvider的poAllowCommandText就可以接受前台來的SQL 命令,並傳送 給TQuery。可以看出,真正傳遞數據的是DataSetProvider的接口,所以,用這 個接口搭建傳遞SQL 的橋梁是必需的。

客戶端進行SQL 查詢的方法是:

ClientDataSet.Close;
ClientDataSet.CommandText := 'SQL語句';
ClientDataSet.Open;

下面通過一個實例來說明方法:

一、SQL 服務器端程序:

首先用上面相同的方法建立一個服務器端COM 工程。工程名取為:PcSQL。

放入一個Query和一個DataSetProvider。

DataSetProvider的屬性Options下的poAllowCommandText=true

這實際上已經建立了一個基於SQL 查詢的服務器端程序。

客戶端查詢服務器端的別名集

在SQL 查詢以前,用戶往往需要指定查詢哪個數據庫,所以需要把服務器上 BDE 數據庫別名(Alias )設置數據抓到前台程序來具體做法我們通過一個實例 來解決:

還是先回到服務器端:

在上述服務器端再建立一個TSession和一個TdataBase(在BDE 頁下)

Session1屬性:

AutoSessionName=true

SessionName=Session1_4 (運行中是可以自動調整的)

KeepConnection=true

Active=true

DataBase1屬性:

AliasName: 連接數據庫別名(比如Mymdb)

DataBaseName: 自定義的名(這裡比如為a)

SessionName: Session1_4 (SessionName值)

如果不希望輸入密碼:

LoginPrompt : false

這樣Query1的連接屬性要變一下,因為它要通過Database來聯接數據庫(以 後在前台可以直接通過改變DataBase的屬性來改變聯接)

DataBaseName= DataBase1的DataBaseName:自定義的名(這裡比如為a)。

SQL屬性輸入一個基本的SQL語言:select * from 獎金 Active=true 表示這 樣也可以連接上了。

TQurey 增加個屬性

SessionName: Session1_4

這樣就可以用Session對象來管理數據源的信息了。

再加入四個Label,顯示當前在線人數,以及進行SQL 查詢的人數。

為了傳給客戶端值,並能夠由客戶端來對服務器端進行某些控制,需要在COM 中加入兩個方法:

View -> Type Library 顯示接口窗口。

右鍵 IPcSQL(具體根據設置名不同而不同) -> New ->建立兩個方法 (Method):

GetDatabaseNames ,用以傳下別名數據,其接口信息為:

Return type :HRESULT
Parameters
name type modifier
Param1 VARIANT* [out,retval]

SetDatabaseName , 用以接受客戶機來的信息,其接口信息要求傳入

三個信息(注意:圖上的SetDatabaseName誤為SetDatabaseNames,實際請設 為SetDatabaseName,否則客戶調用名稱可能會出錯)。

Return type :HRESULT
Parameters
name type modifier
DBName BSTR [in] ----傳上來的別名

(用Add加入)

UserName BSTR [in]
Password BSTR [in]
刷新(Refresh Implementation)以後,就產生兩個函數
function TPcSQL.GetDatabasenames: OleVariant;
begin

end;

procedure TPcSQL.SetDatabasename(const DBname, Username,
Password: WideString);
begin

end;

function TPcSQL.GetDatabaseNames: OleVariant;
var
I: Integer;
DBNames: TStrings;
begin
// 建立一個字符串數組存放BDE所有的數據庫別名數據。
DBNames := TStringList.Create;
try
// 利用Session控件取得當前BDE所有的數據庫別名數據。
Session1.GetDatabaseNames(DBNames);

/// 建立一個變量數組給函數返回變量Result。
Result := VarArrayCreate([0, DBNames.Count - 1], varOleStr);

// 最後再把數據庫別名數據指定給該變量數組。
for I := 0 to DBNames.Count - 1 do
Result[I] := DBNames[I];
Finally

//如果錯誤就把構造的DBNames釋放掉。

DBNames.Free;
end;
end;

上面的函數的關鍵除了取得BDE 數據庫別名以外,還聲明了一個變量數組來 存放數據庫別名數據,所以用VarArrayCreate函數建立一個變量數組,其中參數 1: 指定數組范圍;參數2:數組數據類型,由於別名要通過DCOM傳給前台,數據 類型必須設成varOleStr 才有效。 同時還要說明VarArrayCreate函數是在 Variants單元中建立的,所以,在implementation下還需要聲明:

uses Variants;

否則會編譯出錯,,必要時還要加上Unit1。

第二個過程是客戶機提供上來的聯機數據。

procedure TPcSQL.SetDatabaseName(const DBName, UserName,
Password: WideString);
begin
try
// 把前台傳來的數據庫別名、用戶上線名稱、用戶上線密碼
// 等三項數據指定給TDatabase控件,並且執行聯機的操作。

Database1.Close;
Database1.AliasName := DBName;
if (UserName<>'') and (Password<>'') then begin
Database1.Params.Values['PASSWORD'] := Password;
Database1.Params.Values['USER NAME'] := UserName;
end;
Database1.Open;
except
// 如果聯機時發生錯誤,則產生一個exception給前台程序
// 前台程序將會利用到這個exception來判斷是否要把輸入上
// 線數據的窗口激活。

on E: EDBEngineError do
raise Exception.Create('Password Required') ;
end;
end;

服務器端進行客戶計數

除了上面的功能外,這個程序還加上了一個在線用戶以及查詢用戶統計的功 能。由於這個應用程序執行模式是 Multiple Instance執行模式,所以當某個前 台第一次連上線後,應用程序服務器會激活RemoteDataModule的事件程序,而斷 線後又會執行OnDestroy事件程序,因此就可以用這兩個事件計算連上服務器的 用戶個數。至於Query個數的計算,則由TQuery的OnAfterOpen事件函數判斷。

Form1部分,主要用於顯示

首先在form上安放五個Tlabel控件,其中四個放在一個容器Panel1裡兩個計 數的label分別取名為ClientCount和QueryCount。屬性Capyion=0。注意,下面 的程序在Form1所在的單元Unit1中編寫。

在private下聲明兩個變量:

FQueryCount: Integer;

FClientCount: Integer;

在public下聲明兩個過程

procedure UpdateClientCount(Incr: Integer);

procedure IncQueryCount;

在實現區寫入過程:

procedure Tform1.UpdateClientCount(Incr: Integer);
begin
FClientCount := FClientCount + Incr;
ClientCount.Caption := IntToStr(FClientCount);
end;

procedure Tform1.IncQueryCount;
begin
Inc(FQueryCount);
QueryCount.Caption := IntToStr(FQueryCount);
end;

COM部分

在PcSQL窗口的兩個事件OnCreate和OnDesticy,建立事件驅動程序:

procedure TPcSQL.RemoteDataModuleCreate(Sender: TObject);
begin

end;

procedure TPcSQL.RemoteDataModuleDestroy(Sender: TObject);
begin

end;

procedure TPcSQ.RemoteDataModuleCreate(Sender: TObject);
begin
// 增加一位前台上線者(調用Form1上的程序)
Form1.UpdateClientCount(1);
end;

procedure TPcSQ.RemoteDataModuleDestroy(Sender: TObject);
begin
// 減少一位前台上線者(調用Form1上的程序)
Form1.UpdateClientCount(-1);
end;

在Tqurey的事件AfterOpen建立事件驅動程序:

procedure TPcSQL.Query1AfterOpen(DataSet: TDataSet);
begin

end;

procedure TPcSQ.AdHocQueryAfterOpen(DataSet: TDataSet);
begin
// 新打開一個TQuery查詢(調用Form1上的程序)
Form1.IncQueryCount;
end;

保存,編譯,注冊

二、SQL 客戶端程序

1)建立一個普通的工程。

2)放置一個TDCOMConnrction控件(在Datasnap頁),屬性:

在本機注冊時,可直接設置以下屬性:

ServerName:應用程序服務器注冊名(pro1.pc121)

Connected=true 激活

這時你可以看到服務器端的COM 程序被激活了。

如果在網絡上調試,需要給出服務器名:

ComputerName:服務器名(自動給出網上鄰居)

注意:

ServerGUID的GUID值是自動給出的。

3)放置一個TClientDataSet控件(在Data Access頁)

屬性:

RemoteServer= DCOMConnrction1

ProviderName:=DataSetProvider1

Active=true (激活後將能正常連接)

4)放置TDataSource,屬性:

Dataset:指向ClientDataSet1

安放合適的數據庫綁定控件。放置Demo控件作為SQL 數據輸入用。

procedure TForm1.Button1Click(Sender: TObject);
begin
// 把Memo內用戶輸入的SQL命令,通過IProvide的
// DataRequest方法傳給應用程序服務器。

ClientDataSet1.Close;
ClientDataSet1.CommandText := Memo1.Lines.Text;
ClientDataSet1.Open;

end;

希望用戶直接調用服務器端的別名,可以加入一個ComboBox控件:

form1的Create 事件可以寫成:

procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
DBNames: OleVariant;
begin
// 連上應用程序服務器
DCOMConnection1.Connected := True;

// 取得BDE所有設置的數據庫別名數據
DBNames := DCOMConnection1.AppServer.GetDatabaseNames;

// 假如有數據庫別名數據,則將其一個一個地加到ComboBox控件內
if VarIsArray(DBNames) then
for I := 0 to VarArrayHighBound(DBNames, 1) do
ComboBox1.Items.Add(DBNames[I]);

//把默認的數據庫顯示在第一個
ComboBox1.Text:='mymdb';
end;

運行以後可以看到服務器端的所有的別名:

為了用這個別名列表調用其它的數據庫,考慮到有的數據庫需要輸入密碼和 用戶名,需要加入一個新的Form。

File->New->other->new-form 這就加入了一個

Form2

加入相應的輸入密碼控件。

其中:CheckBox1的屬性 Name=Check ;而兩個Edit控件的Text屬性為空字符 串。

Form2的兩個按鈕事件可以寫成:

procedure TForm2.Button1Click(Sender: TObject);
begin
Check.Checked:=True;
close;
end;
procedure TForm2.Button2Click(Sender: TObject);
begin
Check.Checked:=False;
close;
end;

然後回到form1。

首先在private 區聲明一個函數InputDialog,用於啟動讓用戶輸入上線數據 的Form2。

function InputDialog(var UserName, Password:String):Boolean;

然後在實現區寫入該函數的程序:

// 啟動讓用戶輸入上線數據的Form
function TForm1.InputDialog(var UserName, Password:String):Boolean;
var ret:Boolean;
begin
ret:=False;
try
Form2:=TForm2.Create(Application);
Form2.ShowModal;
UserName:=Form2.Edit1.Text;
Password:=Form2.Edit2.Text;
if Form2.Check.Checked then ret:=True
else ret:=False;
finally
Form2.Free;
Result:=ret;
end;
end;

現在,可以給ComboBox1加入一個雙擊事件:

procedure TForm1.ComboBox1DblClick(Sender: TObject);
var
UserName, Password: string;
begin
// 當您在ComboBox中選取的數據不是空字符串時
if ComboBox1.Text <> '' then
begin
ClientDataSet1.Close;
try
// 先給應用程序服務器傳一次空的UserName及Password

// 如果後台數據庫是Paradox或dBASE可能就沒問題,可是如果是SQL base的 關系型數據庫則會收到exception。

//SetDatabaseNames是服務端的COM 方法

DCOMConnection1.AppServer.SetDatabaseName (ComboBox1.Text,'','');

except

// 由於後台數據庫是SQL base的關系型數據庫,所以您必須通過Form2輸入 UserName及Password,並且把這些數據通過應用程序服務器提供的 SetDatabaseName傳給後台數據庫驗證。

on E: Exception do
if E.Message = 'Password Required' then
begin
if InputDialog(UserName, Password) then
DCOMConnection1.AppServer.SetDatabaseName(ComboBox1.Text,
UserName, Password);
end else raise;
end;
end;
end;

以後就可以利用服務器端的別名列表選擇數據庫了。

雙擊該列表框實際上已經實現了聯接,但是,窗口除關閉掉數據庫顯示以外 ,並沒有其它的反應,這就是說,這個程序使用上還有若干不方便的地方,最重 要的就是當連結上數據庫以後,無法知道表的名字。這樣,也無法方便的構造查 詢數據集的SQL語言。

下面解決這個問題。

現在,在服務器上再做一個方法: GetTableNames ,准備向客戶端送表名。

注意,要用Add加入一個傳出參數。

刷新後產生一個新的方法:

function GetTableNames: OleVariant; safecall;

現在可以寫方法程序了:

function TPcSQL.GetTableNames: OleVariant;
var
I: Integer;
DBTables: TStrings;
begin
// 建立一個字符串數組存放所有表名。
DBTables := TStringList.Create;
try
// 利用Database控件取得當前所有表名數據。
Database1.GetTableNames(DBTables , False);

/// 建立一個變量數組給函數返回變量Result。
Result := VarArrayCreate([0, DBTables.Count - 1], varOleStr);

// 最後再把表名數據指定給該變量數組。

for I := 0 to DBTables.Count - 1 do
Result[I] := DBTables[I];

finally
DBTables.Free;
end;

end;

然後重新保存,注冊,運行一下。

再看客戶端:

現在給它再加一個ComboBox,以顯示當前打開的數據庫的表名。

; 在.FormCreate事件過程中,再聲明一個變量:

DBTables: OleVariant;

在.FormCreate程序的最後加上:

//顯示表名
ComboBox2.Clear;
DBTables := DCOMConnection1.AppServer.GetTableNames;
if VarIsArray(DBTables) then
for I := 0 to VarArrayHighBound(DBTables, 1) do
ComboBox2.Items.Add(DBtables[I]);

為了在改變數據庫後也能正確顯示相應的表名,ComboBox1的雙擊事件也要加 上相應的一段。

procedure TForm1.ComboBox1DblClick(Sender: TObject);
var
UserName, Password: string;
//下面兩的變量聲明是新加的
DBTables: OleVariant;
i:integer;
begin
// 當您在ComboBox中選取的數據不是空字符串時
if ComboBox1.Text <> '' then
begin
ClientDataSet1.Close;
try
// 先給應用程序服務器傳一次空的UserName及Password
// 如果後台數據庫是Paradox或dBASE可能就沒問題,
// 可是如果是SQL base的關系型數據庫則會收到exception。
//SetDatabaseNames是服務端的COM 方法

DCOMConnection1.AppServer.SetDatabaseName(ComboBox1.Text,'','');
except

// 由於後台數據庫是SQL base的關系型數據庫,所以您必須
// 通過Form2輸入UserName及Password,並且把這些數據
// 通過應用程序服務器提供的SetDatabaseName傳給後台數
// 據庫驗證。

on E: Exception do
if E.Message = 'Password Required' then
begin
if InputDialog(UserName, Password) then
DCOMConnection1.AppServer.SetDatabaseName(ComboBox1.Text,
UserName, Password);
end else raise;
end;

//顯示表名,這段程序是新加的
ComboBox2.Clear;
DBTables := DCOMConnection1.AppServer.GetTableNames;
if VarIsArray(DBTables) then
for I := 0 to VarArrayHighBound(DBTables, 1) do
ComboBox2.Items.Add(DBtables[I]);
ComboBox2.Text:=ComboBox2.items[0];
end;
end;

給ComboBpx2加一個雙擊事件,可以把表的名字加入SQL 語言:

procedure TForm1.ComboBox2DblClick(Sender: TObject);
begin
Memo1.text:=Memo1.text+combobox2.text;
end;

效果就完全不同了,你可以方便的選擇數據庫和表:

然後組合適當的SQL語言,最終打開一個合適的表。

進一步考慮,組合新的SQL語言的時候最好要有字段名的數據,這不需要從服 務器得到,因為在多層情況下,ClientDataSet實際上起著Ttable或者Tquery相 似的作用,對數據庫的控制上,有幾乎相同的語言,例如記錄指針的移動,字段 數據的取得和寫入等等,這樣一來,你也可以直接使用這些方法來操縱數據庫。

再一次提醒大家,ClientDataSet是個功能強大非常重要的控件,在Delphi的 很多高級場合,都要使用到它。

當然,利用ClientDataSet,被打開數據庫的字段名表也很容易得到。

首先再加一個Combbox控件Combbox3。

把SQL 查詢的Button事件作如下修改:

procedure TForm1.Button1Click(Sender: TObject);
//首先要定義兩個變量
var i,N:integer;
begin
ClientDataSet1.Close;
ClientDataSet1.CommandText := Memo1.Lines.Text;
ClientDataSet1.Open;

//這是後加入的顯示子段名的程序

ComboBox3.Clear;
N:=ClientDataSet1.FieldCount;
for I := 0 to N - 1 do
ComboBox3.Items.Add(ClientDataSet1.Fields[i].FieldName);
ComboBox3.Text:=ComboBox3.items[0];

end;

事實上,FormCreate事件程序也要加入相應的程序,使一打開程序就有子段 名表顯示。

procedure TForm1.FormCreate(Sender: TObject);
var
//增加兩個定義
I,N: Integer;
DBNames: OleVariant;
DBTables: OleVariant;
begin
// 連上應用程序服務器
DCOMConnection1.Connected := True;

// 取得BDE所有設置的數據庫別名數據
DBNames := DCOMConnection1.AppServer.GetDatabaseNames;

// 假如有數據庫別名數據,則將其一個一個地加到ComboBox控件內
if VarIsArray(DBNames) then
for I := 0 to VarArrayHighBound(DBNames, 1) do
ComboBox1.Items.Add(DBNames[I]);

//把默認的數據庫顯示在第一個
ComboBox1.Text:='abc';

//顯示表名
DBTables := DCOMConnection1.AppServer.GetTableNames;
if VarIsArray(DBTables) then
for I := 0 to VarArrayHighBound(DBTables, 1) do
ComboBox2.Items.Add(DBtables[I]);
ComboBox2.Text:=ComboBox2.items[0];

//顯示字段名表,增加的程序
ComboBox3.Clear;
N:=ClientDataSet1.FieldCount;
for I := 0 to N - 1 do
ComboBox3.Items.Add(ClientDataSet1.Fields[i].FieldName);
ComboBox3.Text:=ComboBox3.items[0];
end; 

需要時,也可以寫入ComboBox3的雙擊事件:

好了,現在可以看看效果:

這些數據取得以後,你就可以把這個客戶程序做得更加方便通用,那就要看 你的想象力和程序設計的水平了,這裡的例子,主要是想提供客戶端使用DCOM提 供的方法的思想和規則,通過這樣的研究,您是不是對DCOM 和分布式數據庫的 問題有了更深的理解了呢?

第七節一對多表的服務器端程序

一、Application Server

事先要有一個標准的

放入 ADOTable1

ADOTable2
Datasouce1
DataSetProvider1(在
Data Access)
DataSetProvider2

屬性:

ADOTable1(主表)

ADOTable2(從表)

用ConnectionString和TableName連上數據庫

為了構建主從表,注意下面的圖:

DataSouce1屬性:DataSet=ADOTab1

ADOTable2屬性:MasterSource=DataSource1

MasterField=連結字段

TdataSetProvider1屬性:Dataset=ADOTable1

TdataSetProvider2屬性:Dataset=ADOTable2

這就完成了主從表的服務器端構建。

保存,注冊,運行一下。

二、客戶端編程

首先建立一個普通的工程:

加入一個TDCOMConnection,兩個TClientDataSet,和兩個TdataSource。

DCOMConnection1

屬性:ComputerName=計算機名(本地可以不寫)

ServerName=服務器端程序名(COM)
Connecter=true (表示連上)
ClientDataSet1

屬性:RemoteServer=DCOMConnection1

ProviderName=DataSetProvider1
Active-=true
ClientDataSet2
屬性:RemoteServer=DCOMConnection2

ProviderName=DataSetProvider2

Active-=true

兩個TDataSource分別連上

ClientDataSet1和

ClientDataSet2

加入兩個TDBGrid,分別連上相應的DataSource。

主從表似乎應該建立起來了,但運行一下就可以發現,主從關系並沒有建立 起來。

解決辦法:

雙擊ClientDataSet1,出現一個Form1.ClientDataSet1窗口:

在窗口空白處右鍵,快捷菜單上選Add Field 這時,我們可以看到最後一行 出現:ADOTable2 。這是很重要的,正是靠這個建立了主從聯接關系

OK 讓它進入Form1.ClientDataSet1窗口(也可以調整主表所顯示的字段,但 ADOTable2 不能少,它實際上是把從表作為主表的一個字段使用了)。

ClientDataSet2屬性:

DataSetField=ClientDataSet1ADOTable2

Active=true

好啦,主從關系建立起來了。

通過這個研究可以發現,實際上在服務端程序中,DataSetProvider2是多余 的,以後在設置的時候,可以不要。只需要一個DataSetProvider1就可以和客戶 端聯系了。

Form
file -> New -> Multitier -> Remote Data Module
CoClass Name : Master2
Instacting: Multiple Instacting
Threading Model: Apartment
procedure TForm1.ComboBox3DblClick(Sender: TObject);
begin
Memo1.text:=Memo1.text+combobox3.text;
end;

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved