Search code examples
delphidbexpress

How to mark all TClientDataSet records as Inserted?


I have a complex transaction that saves data from multiple TClientDataSets in database.

One of those ClientDataSets always append data to underlaying table, eg. generate INSERT statements, regardless of where existing records came from.

I'm forcing inserts right now with:

// Create temp table, assign all target data, 
// Empty target table, append data from temp

Tmp := TClientDataSet.Create; 
Tmp.Data := Table.Data; 

Table.MergeChangeLog; 
Table.EmptyDataSet;

Tmp.First; 
// Append all records 
While not Tmp.Eof do
begin
  Table.Append;  
  for i := 0 to Table.FieldCount - 1 do
    Table.Fields[i].Value := Tmp.Fields[i].Value 
  Table.Post; 

  Tmp.Next;
end;

Tmp.Free;

Is there a simpler way to just mark all records as inserted?


Solution

  • One would hope sth. like this would work (at least when there are no calculated fields);

    uses
      dsintf;
    
    [..]
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      ClientDataSet1.First;
      while not ClientDataSet1.Eof do begin
        PRecInfo(ClientDataSet1.ActiveBuffer +
            ClientDataSet1.RecordSize).Attribute := dsRecNew;
        ClientDataSet1.Next;
      end;
    end;
    

    Well, it works, but as long as no data is re-retrieved. That is, TCustomClientDataSet.GetRecord re-sets the record attributes. This renders the hacky approach pretty useless I guess.

    Maybe one could try binding a data-aware control and see if setting the BufferCount of the DataLink of the DataSource would help. Or, try overriding GetRecord in a descendant ClientDataSet. But I doubt it is worth the effort.

    [EDIT]

    The record attribute can be hacked in an "AfterScroll" event. This would help, for instance, code testing the UpdateStatus to return "usInserted".

    procedure TForm1.ClientDataSet1AfterScroll(DataSet: TDataSet);
    begin
      PRecInfo(DataSet.ActiveBuffer + DataSet.RecordSize).Attribute := dsRecNew;
    end;
    

    Test if all the records are inserted

    procedure TForm1.Button1Click(Sender: TObject);
    begin
      ClientDataSet1.First;
      while not ClientDataSet1.Eof do begin
        if not (ClientDataSet1.UpdateStatus = usInserted) then
          raise Exception.Create('The record is not inserted');
        ClientDataSet1.Next;
      end;
    end;
    



    A new TClientDataSet can be derived to retrieve always "inserted" records.

    (The type declaration goes before the form/datamoule containing the ClientDataSet)

    type
      TClientDataset = class(dbclient.TClientDataSet)
        function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean):
            TGetResult; override;
      end;
    [..]
    implementation
    
    function TClientDataset.GetRecord(Buffer: PChar; GetMode: TGetMode;
      DoCheck: Boolean): TGetResult;
    begin
      Result := inherited GetRecord(Buffer, GetMode, DoCheck);
      if Result = grOk then
        PRecInfo(Buffer + RecordSize).Attribute := dsRecNew;
    end;
    

    Now for all records, UpdateStatus will return "usInserted".

    EDIT

    I finally understood that the aim is to generate insert SQLs for all records. We won't achieve this by modifying records' attributes of the DataSet, ApplyUpdates takes only the "Delta" into consideration and we possibly have no Delta. There might be different ways to achieve this, the below example assumes the DataSet has a Provider and we are able to put an event handler on it.

    type
      TForm1 = class(TForm)
        [..]
      private
        procedure DeltaAfterScroll(DataSet: TDataSet);
        [..]
    
    implementation
    
    procedure TForm1.DeltaAfterScroll(DataSet: TDataSet);
    begin
    // The UpdateTree of the Resolver of the Provider will visit each 
    // record to get the UpdateStatus
      PRecInfo(DataSet.ActiveBuffer + DataSet.RecordSize).Attribute := dsRecNew;
    end;
    
    
    type
      TAccessCCDS = class(TCustomClientDataSet);
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      Count: Integer;
    begin
      ClientDataSet1.MergeChangeLog;
    // Since there's no "Delta", ApplyUpdates will return immediately.
    // Hence, we'll force an update by calling DoApplyUpdates, bypassing the
    // ChangeCount test, and update with the "Data".
    // Reconcilation is left out for simplicity.
      TAccessCCDS(ClientDataSet1).DoApplyUpdates(ClientDataSet1.Data, 0, Count);
    end;
    
    procedure TForm1.DataSetProvider1UpdateData(Sender: TObject;
      DataSet: TCustomClientDataSet);
    begin
    // Will be called once when ApplyUpdates is called.
      TAccessCCDS(DataSet).AfterScroll := DeltaAfterScroll;
    end;