RAD Studio |  |

Supporting State Information in Remote Data Modules
See Also
Collapse All

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 between 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:

Copy Code

    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:

Copy Code

    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;

Copy Code

    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:

Copy Code

    TRemoteDataModule1.DataSetProvider1AfterGetRecords(Sender: TObject;
                       var OwnerData: OleVariant);
    begin
      with Sender as TDataSetProvider do
        OwnerData := Tag; {send the last value of CUST_NO }
    end;

Copy Code

    TRemoteDataModule1::DataSetProvider1AfterGetRecords(TObject *Sender, OleVariant &OwnerData)
    {
      TComponent *pProvider = dynamic_cast<TComponent *>(Sender);
      OwnerData = pProvider->Tag;
    }

On the client, the client dataset saves this last value of CUST_NO:

Copy Code

    TDataModule1.ClientDataSet1AfterGetRecords(Sender: TObject; var OwnerData: OleVariant);
    begin
      with Sender as TClientDataSet do
        Tag := OwnerData; {save the last value of CUST_NO }
    end;

Copy Code

    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:

Copy Code

    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;

Copy Code

    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 uses the last CUST_NO sent as a minimum value in the query:

Copy Code

    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;

Copy Code

    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
    }
    }

See Also

Managing Transactions in Multi-tiered Applications

Extending the Application Servers Interface

Supporting Master/detail Relationships

Supporting State Information in Remote Data Modules

Setting Up the Remote Data Module

Writing Web-based Client Applications

Communicating with The Client Dataset

Using Multiple Remote Data Modules

Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
What do you think about this topic? Send feedback!