views:

342

answers:

1

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?

+1  A: 

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;
Sertac Akyuz
This is not working. I have no calculated or lookup fields.
dmajkic
As I told in the answer this won't work if you navigate away from the hacked record and BufferCount is 1. Since I don't know what you're doing with the "usInserted" records it is difficult to guess if -tricking code to believe records are inserted while they aren't- will serve to your purpose. I'll edit the answer anyway...
Sertac Akyuz
After setting all records to inserted I do ApplyUpdates(0). In fact, all I want is "INSERT INTO TABLE ..." generated for every row in the ClientDataSet.
dmajkic
Since there is no simple answer, and you presented a few nice ideas I'll accept your answer. I hope that Embarcadero is reading this, I stay.
dmajkic
Marking records as Inserted will not help there. ApplyUpdates applies the "Delta" not "Data". Possibly we have no delta here, otherwise you wouldn't be trying this. I'll edit once more. My, this is gonna be one long answer. <g>
Sertac Akyuz