Portuguese notice: Escrevi esse artigo em inglês pois o objetivo inicial é ajudar usuários do news da RemObjects com essa dúvida frequente. Mas acredito que está bem fácil de entender, mesmo para quem não domina o idioma. Em todo caso, fiquem livres para postar comentários em português pedindo esclarecimentos de algo que não entenderam.

One of the first tricky things you face off when migrating from 2 to 3 tiers is to get rid of all transaction control on the client side. The client should not start or finish a transaction. As a matter of fact, the client should know nothing at all about transactions. All transaction logic should remain on the server.

This post is intended to show how to create a method in your RemObjects DataSnap server to receive a list of ClientDataSet deltas to update on the server inside a single transaction so that you can rollback all changes in case of an unexpected error during the updates.

This method is useful only when you need to apply ClientDataSets that are not nested, because DataSnap already applies nested datasets in a single transaction by default.

You’re going to need a working RO DataSnap server, since I’m not going to cover the steps involved in building one. We are just going to add a new method to your existing service.

First you need to create some data types in your server RODL. Using RO Service Builder, create a DeltaToApply struct with ProviderName  (string) and Delta (binary) fields:

Then create an array of DeltaToApply:

Then you have to create a method in your service that takes the array parameter. I always add this method to the same service that has all my providers, because we are going to need them to apply the updates:

The implementation of this method is as follow: 

<span style="color: #0000ff">procedure</span> TNewService.ApplyUpdates(<span style="color: #0000ff">var</span> ADeltaArray: DeltaArray);
<span style="color: #0000ff">var</span>
  I: Integer;
  Provider: TDataSetProvider;
  ErrorCount: Integer;
<span style="color: #0000ff">begin</span>
  <span style="color: #008000">// Put your code to start transaction</span>
  <span style="color: #0000ff">try</span>
    <span style="color: #0000ff">for</span> I := 0 <span style="color: #0000ff">to</span> ADeltaArray.Count - 1 <span style="color: #0000ff">do</span>
    <span style="color: #0000ff">begin</span>
      Provider := FindProvider(ADeltaArray[I].ProviderName);
      <span style="color: #0000ff">if</span> <span style="color: #0000ff">not</span> Assigned(Provider) <span style="color: #0000ff">then</span>
        <span style="color: #0000ff">raise</span> Exception.Create('Provider <span style="color: #0000ff">not</span> found: ' + ADeltaArray[I].ProviderName);

      Provider.ApplyUpdates(VariantFromBinary(ADeltaArray[I].Delta), 0, ErrorCount);
      <span style="color: #0000ff">if</span> ErrorCount > 0 <span style="color: #0000ff">then</span>
        <span style="color: #008000">// Put your code to handle errors</span>
        <span style="color: #0000ff">raise</span> Exception.Create('Errors during applyupdates: ' + Provider.Name);
    <span style="color: #0000ff">end</span>;
    <span style="color: #008000">// Put your code to commit the transaction</span>
  <span style="color: #0000ff">except</span>
    <span style="color: #008000">// Put your code to rollback the transaction</span>
    <span style="color: #0000ff">raise;</span>
  <span style="color: #0000ff">end</span>;
<span style="color: #0000ff">end</span>;

I have also created a helper function to find the provider by its name:

<span style="color: #0000ff">function</span> TNewService.FindProvider(ProviderName: <span style="color: #0000ff">string</span>): TDataSetProvider;
<span style="color: #0000ff">var</span>
  Component: TObject;
<span style="color: #0000ff">begin</span>
  Component := FindComponent(ProviderName);
  <span style="color: #0000ff">if</span> Component is TDataSetProvider <span style="color: #0000ff">then</span>
    Result := Component as TDataSetProvider
  <span style="color: #0000ff">else</span>
    Result := <span style="color: #0000ff">nil</span>;
<span style="color: #0000ff">end</span>;

Ok, this is it for the server. On the client, you need to create a method that adds all ClientDataSet deltas to your array and sends it to the server:

<span style="color: #0000ff">procedure</span> TClientForm.ApplyUpdates(ClientDataSets: <span style="color: #0000ff">array</span> <span style="color: #0000ff">of</span> TClientDataSet);
<span style="color: #0000ff">var</span>
  Deltas: DeltaArray;
  Delta: DeltaToApply;
  I: Integer;
<span style="color: #0000ff">begin</span>
  Deltas := DeltaArray.Create;
  try
    <span style="color: #0000ff">for</span> I := Low(ClientDataSets) <span style="color: #0000ff">to</span> High(ClientDataSets) <span style="color: #0000ff">do</span>
    <span style="color: #0000ff">begin</span>
      <span style="color: #0000ff">if</span> ClientDataSets[I].ChangeCount = 0 <span style="color: #0000ff">then</span>
        Continue;

      Delta := Deltas.Add;
      Delta.ProviderName := ClientDataSets[I].ProviderName;
      Delta.Delta := BinaryFromVariant(ClientDataSets[I].Delta);
    <span style="color: #0000ff">end</span>;
    CoNewService.Create(ROMessage, ROChannel).ApplyUpdates(Deltas);
  finally
    Deltas.Free;
  <span style="color: #0000ff">end</span>;
<span style="color: #0000ff">end</span>;

The VariantFromBinary and BinaryFromVariant are located in the uROBinaryHelpers unit.

Then you just need to call this method from the client passing all ClientDataSets you want to update on the server using a single transaction:

ApplyUpdates([ClientDataSet1, ClientDataSet2, ClientDataSet3]);

That’s it! I hope it helps. If you find any problems or improvements to this code, please, let me know ASAP so that I can fix.