上篇建立了基礎「應用程序服務器」(COM + DataSetProvider) 與「瘦客戶端」(TClientDataSet + TDCOMConnection)。本篇深入解決三大核心需求:
關鍵設置:服務端 DataSetProvider.Options.poAllowCommandText := True。客戶端代碼:
ClientDataSet1.Close;
ClientDataSet1.CommandText := 'SELECT * FROM Orders WHERE Amount > 1000'; // 任意 SQL
ClientDataSet1.Open; // 自動傳給服務端 DataSetProvider 執行並返回結果集
原理:CommandText 通過 IProvider 接口傳遞給服務端 DataSetProvider,後者將其赋值給內部 TQuery.SQL 並 Open,結果數據包打包返回填充 ClientDataSet。
在類型庫編輯器中添加兩個方法:
GetDatabaseNames: OleVariant (返回字符串數組)SetDatabaseName(const DBName, UserName, Password: WideString)實現代碼:
function TRemoteDataModule.GetDatabaseNames: OleVariant;
var DBNames: TStringList; I: Integer;
begin
DBNames := TStringList.Create;
try
Session1.GetDatabaseNames(DBNames); // BDE Session 獲取所有別名
Result := VarArrayCreate([0, DBNames.Count-1], varOleStr); // 必須用 varOleStr 跨 COM
for I := 0 to DBNames.Count-1 do Result[I] := DBNames[I];
finally DBNames.Free; end;
end;
procedure TRemoteDataModule.SetDatabaseName(const DBName, UserName, Password: WideString);
begin
try
Database1.Close;
Database1.AliasName := DBName;
if (UserName <> '') and (Password <> '') then
begin
Database1.Params.Values['USER NAME'] := UserName;
Database1.Params.Values['PASSWORD'] := Password;
end;
Database1.Open; // 嘗試連接,失敗拋出 EDBEngineError
except
on E: EDBEngineError do raise Exception.Create('Password Required'); // 統一異常文本供客戶端捕獲
end;
end;
ComboBox1:顯示別名列表 (FormCreate 時調用 AppServer.GetDatabaseNames 填充)。ComboBox1 觸發切換:先嘗試空密碼調用 SetDatabaseName,捕獲 'Password Required' 異常彈出登錄對話框 (Form2),獲取用戶名/密碼後重試。ComboBox2 (表名列表) 和 ComboBox3 (字段名列表)。procedure TForm1.ComboBox1DblClick(Sender: TObject);
var UserName, Password: string; DBTables: OleVariant; I: Integer;
begin
if ComboBox1.Text = '' then Exit;
ClientDataSet1.Close;
try
DCOMConnection1.AppServer.SetDatabaseName(ComboBox1.Text, '', ''); // 嘗試無密碼
except
on E: Exception do
if E.Message = 'Password Required' then
begin
if InputDialog(UserName, Password) then // 彈出 Form2 獲取憑證
DCOMConnection1.AppServer.SetDatabaseName(ComboBox1.Text, UserName, Password)
else Exit;
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];
// 刷新字段名 (基於當前 ClientDataSet 打開的表)
RefreshFieldList;
end;
function TRemoteDataModule.GetTableNames: OleVariant;
var Tables: TStringList; I: Integer;
begin
Tables := TStringList.Create;
try
Database1.GetTableNames(Tables, False); // BDE Database 獲取當前連接的表名
Result := VarArrayCreate([0, Tables.Count-1], varOleStr);
for I := 0 to Tables.Count-1 do Result[I] := Tables[I];
finally Tables.Free; end;
end;
ADOTable1 (主表) + ADOTable2 (從表)。DataSource1.DataSet := ADOTable1;ADOTable2.MasterSource := DataSource1;ADOTable2.MasterFields := 'KeyField'。DataSetProvider1.DataSet := ADOTable1。從表不需單獨 Provider。ClientDataSet1.RemoteServer := DCOMConnection1; ProviderName := 'DataSetProvider1'; Active := True。ClientDataSet1 打開字段編輯器,右鍵 Add Fields,勾選所有主表字段,**關鍵:最後一行會出現「ADOTable2」(從表名)**,將其加入。這將從表作為主表的一個 TDataSetField 字段。ClientDataSet2.DataSetField := ClientDataSet1ADOTable2 (即主表 ClientDataSet 中那個 DataSetField);ClientDataSet2.Active := True。結果:ClientDataSet1 導航時,ClientDataSet2 自動過濾顯示對應從表記錄。雙綁定 DBGrid 即可呈現經典主從界面。
| 誤區 | 正確認知 |
|---|---|
| 以為每個表都需要一個 DataSetProvider | 主從關係在服務端由 MasterSource/MasterFields 維護,客戶端只需一個 Provider 對應主表,從表通過 DataSetField 關聯獲取。 |
| 以為 CommandText 只能用於單表 | CommandText 可為任意合法 SQL (含 JOIN),服務端 Query 執行返回平坦結果集,客戶端視為單表。 |
| 以為動態切換數據庫需重啟服務 | SetDatabaseName 運行時修改 Database.AliasName/Params 並 Open 即時生效,無需重啟 COM。 |
本篇實現了多層架構的三大進階能力:SQL 直通賦予客戶端極大查詢靈活性;動態數據庫切換解決多租戶/多數據源部署需求;主從表自動關聯證明了 DataSnap/MIDAS 完整支持層次化數據呈現。掌握 CommandText、AppServer 方法調用、DataSetField 關聯機制,是構建企業級多層 Delphi 應用的核心技能。