Supporting State Information in Exposed Data Modules
Go Up to Creating the Server Application
The IAppServer interface, which client datasets use to communicate with providers on the application server, is mostly stateless. When an application is stateless, it does not "remember" anything that happened in previous calls by the client. This stateless quality is useful if you are pooling database connections in a transactional data module because your application server does not need to distinguish between database connections for persistent information such as record currency. Similarly, this stateless quality is important when you are sharing remote data module instances among many clients, as occurs with just-in-time activation or object pooling. SOAP data modules must be stateless.
However, there are times when you want to maintain state information between calls to the application server. For example, when requesting data using incremental fetching, the provider on the application server must "remember" information from previous calls (the current record).
Before and after any calls to the IAppServer interface that the client dataset makes (AS_ApplyUpdates, AS_Execute, AS_GetParams, AS_GetRecords, or AS_RowRequest), it receives an event where it can send or retrieve custom state information. Similarly, before and after providers respond to these client-generated calls, they receive events where they can retrieve or send custom state information. Using this mechanism, you can communicate persistent state information between client applications and the application server, even if the application server is stateless.
For example, consider a dataset that represents the following parameterized query:
SELECT * from CUSTOMER WHERE CUST_NO > :MinVal ORDER BY CUST_NO
To enable incremental fetching in a stateless application server, you can do the following:
When the provider packages a set of records in a data packet, it notes the value of CUST_NO on the last record in the packet:
TRemoteDataModule1.DataSetProvider1GetData(Sender: TObject; DataSet: TCustomClientDataSet); begin DataSet.Last; { move to the last record } with Sender as TDataSetProvider do Tag := DataSet.FieldValues['CUST_NO']; {save the value of CUST_NO } end;
TRemoteDataModule1::DataSetProvider1GetData(TObject *Sender, TCustomClientDataSet *DataSet) { DataSet->Last(); // move to the last record TComponent *pProvider = dynamic_cast<TComponent *>(Sender); pProvider->Tag = DataSet->FieldValues["CUST_NO"]; }
The provider sends this last CUST_NO value to the client after sending the data packet:
TRemoteDataModule1.DataSetProvider1AfterGetRecords(Sender: TObject; var OwnerData: OleVariant); begin with Sender as TDataSetProvider do OwnerData := Tag; {send the last value of CUST_NO } end;
TRemoteDataModule1::DataSetProvider1AfterGetRecords(TObject *Sender, OleVariant &OwnerData) { TComponent *pProvider = dynamic_cast<TComponent *>(Sender); OwnerData = pProvider->Tag; }
In the client dataset, the client saves this last value of CUST_NO:
TDataModule1.ClientDataSet1AfterGetRecords(Sender: TObject; var OwnerData: OleVariant); begin with Sender as TClientDataSet do Tag := OwnerData; {save the last value of CUST_NO } end;
TDataModule1::ClientDataSet1AfterGetRecords(TObject *Sender, OleVariant &OwnerData) { TComponent *pDS = dynamic_cast<TComponent *>(Sender); pDS->Tag = OwnerData; }
Before fetching a data packet, the client sends the last value of CUST_NO it received:
TDataModule1.ClientDataSet1BeforeGetRecords(Sender: TObject; var OwnerData: OleVariant); begin with Sender as TClientDataSet do begin if not Active then Exit; OwnerData := Tag; { Send last value of CUST_NO to application server } end; end;
TDataModule1::ClientDataSet1BeforeGetRecords(TObject *Sender, OleVariant &OwnerData) { TClientDataSet *pDS = dynamic_cast<TClientDataSet *>(Sender); if (!pDS->Active) return; OwnerData = pDS->Tag; }
Finally, on the server, the provider inserts in the query the CUST_NO value that was sent, as the minimum value:
TRemoteDataModule1.DataSetProvider1BeforeGetRecords(Sender: TObject; var OwnerData: OleVariant); begin if not VarIsEmpty(OwnerData) then with Sender as TDataSetProvider do with DataSet as TSQLDataSet do begin Params.ParamValues['MinVal'] := OwnerData; Refresh; { force the query to reexecute } end; end;
TRemoteDataModule1::DataSetProvider1BeforeGetRecords(TObject *Sender, OleVariant &OwnerData) { if (!VarIsEmpty(OwnerData)) { TDataSetProvider *pProv = dynamic_cast<TDataSetProvider *>(Sender); TSQLDataSet *pDS = (dynamic_cast<TSQLDataSet *>(pProv->DataSet); pDS->Params->ParamValues["MinVal"] = OwnerData; pDS->Refresh(); // force the query to reexecute } }