Description of low-level FireDac objects in TDataSet

Data editing

Posted by: Alex A. Publish date: 31.05.2022

All the methods that we will consider in this article and that we have already analyzed in the previous blog post are appropriate for special cases and general understanding. In a common work process, it is enough to have a well-documented program interface TFDDataSet. But direct editing can be useful, for example, in those situations when it is necessary to avoid adding extra events in DB-Aware components that appear due to “simple” editing or when you want to conduct changes without affecting changelog. It can be useful to view changelog for showing a list of changes to a user before actual saving and for provide the possibility to cancel some actions (it can be also done with the help of TFDDataSet.StatusFilter) as well as for recording them in some log. Temporary reset of flags that prohibit editing is useful in those cases when you need to avoid strict dependency between the metadata that are returned by the database client and the data in TFDDataSet while saving these data themselves (for example, the length of text fields).

Editing

In the previous article, we’ve explained how to read data without turning to TDataSet. Now let’s take a look at records and add records to TFDDataSet with the help of the Table object

    var mtTable := TFDMemTable.Create(nil);
    try
  mtTable.CachedUpdates := True;
  mtTable.FieldDefs.Add(‘Caption’, ftString, 50);
  mtTable.FieldDefs.Add(‘version’, ftInteger);
  mtTable.Open;
 //let’s create objects of columns
  var oCaptionColumn := mtTable.Table.Columns.ColumnByName(‘Caption’);
  var oVersionColumn := mtTable.Table.Columns.ColumnByName(‘version’);

//that’s a new line. Pay attention that its State will be rsDetached and it won’t be included in the Table
  var oRow := mtTable.Table.NewRow;
  oRow.ValueO[oCaptionColumn] := ‘Row 1’; //let’s assign the value   oRow.ValueO[oVersionColumn] := 1;
  mtTable.Table.Rows.Add(oRow); /add to the Table

  mtTable.Resync([]); // it is necessary to ensure that DataSet will work correctly       DoSomething(mtTable);   finally     FreeAndNil(mtTable);   end;    

Let’s edit data

//let’s update the first record
      var oRow := mtTable.Table.Rows[0];
      oRow.BeginEdit;
      oRow.ValueS[‘version’] := 2;
      oRow.EndEdit;
//pay attention that we can indicate the parameter ANoVersion = True  and editing will take place without turning to //changelog, and RowState will be rsUnchanged

When you are adding massive line changes, you can use the BeginLoadData \EndLoadData methods that will turn off rebuilding of the built View for the time of modifications.

    var State: TFDDatsLoadState;
  mtTable.Table.BeginLoadData(State);
    try
      var oRow := mtTable.Table.NewRow;
      oRow.ValueO[oCaptionColumn] := ‘Row 1’;
      oRow.ValueO[oVersionColumn] := 1;
      mtTable.Table.Rows.Add(oRow);

      oRow := mtTable.Table.NewRow;
      oRow.ValueO[oCaptionColumn] := ‘Row 2’;
      oRow.ValueO[oVersionColumn] := 1;
      mtTable.Table.Rows.Add(oRow);

      oRow := mtTable.Table.NewRow;
      oRow.ValueO[oCaptionColumn] := ‘Row 3’;
      oRow.ValueO[oVersionColumn] := 1;
      mtTable.Table.Rows.Add(oRow);
    finally
    mtTable.Table.EndLoadData(State);
    end;

After any manipulations of this type, it is necessary to update the related TDataSet states by calling Resync. Otherwise, the data provided by, for example, DB-Aware components will be incorrect. All the mentioned methods will take the value of the Variant type for entry.

Manual processing of the changelog

Change accounting on the TFDDataSet level is executed only when the CachedUpdates option is turned on. It’s the responsibility of the TFDDatSUpdatesJournal class, the copy of which is available only via the TFDDatsTable.Updates property (if it is used without datasets) and it can belong to the special update manager TFDDatSManager that is provided by the TFDTableAdapter class and it connects requests to the database and TFDMemTable. For TFDRdbmsDataSet  successors, everything is already ensured in the set.

A changelog is a list of TFDatsRow rows that were changed, added or deleted. The record has a RowState property that indicates the state of the record.

Let’s have a look at the log in the following example.

    var mtTable := TFDMemTable.Create(nil);
    try
  mtTable.CachedUpdates := True;
  mtTable.FieldDefs.Add(‘Caption’, ftString, 50);
  mtTable.FieldDefs.Add(‘version’, ftInteger);
  mtTable.Open;

  mtTable.Append;
  mtTable.FieldByName(‘Caption’).AsString := ‘Row 1’;
mtTable.FieldByName(‘version’).AsInteger := 1;
  mtTable.Post;

  mtTable.Append;
  mtTable.FieldByName(‘Caption’).AsString := ‘Row 2’;
  mtTable.FieldByName(‘version’).AsInteger := 1;
  mtTable.Post;

  mtTable.Append;
  mtTable.FieldByName(‘Caption’).AsString := ‘Row 3’;
  mtTable.FieldByName(‘version’).AsInteger := 1;
  mtTable.Post;

  mtTable.CommitUpdates; // let’s clean the log so that all records will be “unchanged”

  //let’s update the first record
  mtTable.First;
  mtTable.Edit;
  mtTable.FieldByName(‘version’).AsInteger := 2;
  mtTable.Post;

  //let’s delete the second record
  mtTable.Next;
  mtTable.Delete;

  //let’s add the fourth record
  mtTable.Append;
  mtTable.FieldByName(‘Caption’).AsString := ‘Row 4’;
  mtTable.FieldByName(‘version’).AsInteger := 2;
  mtTable.Post;

  var oRow := mtTable.DatSManager.Updates.FirstChange(mtTable.Table);
  while Assigned(oRow) do
  begin
        //let’s show all records
          var sMessage: String := VarToStr(oRow.ValueS[‘Caption’]) + ‘ version ‘ +            VarToStr(oRow.ValueS[‘version’]);
        case oRow.RowState of
          rsInserted: sMessage := ‘Inserted:  ‘ + sMessage;
        rsDeleted:  sMessage := ‘Deleted:  ‘ + sMessage;
        rsModified: sMessage := ‘Modified:  ‘ + sMessage;
    end;
    ShowMessage(sMessage);
    oRow := mtTable.DatSManager.Updates.NextChange(oRow);
      end;
    finally
  FreeAndNil(mtTable);
    end;

We also can use methods that will show the changelog in the form of the list TFDDatsRow –  GetChanges and GetChangesView. The difference between them is the following: GetChangesView demonstrates the ongoing changes and it changes itself if we change data. And GetChanges creates an object TFDDatsTable with immediate demonstration of changes. Both functions accept TDDatSRowStates allowing us to assign only deleted or only changed records.

var oLog := mtTable.Table.GetChangesView;
for var i := 0 to oLog.Rows.Count – 1 do
begin
  oRow := oLog.Rows[i];
  var sMessage: String := VarToStr(oRow.ValueS[‘Caption’]) + ‘ version ‘ +     VarToStr(oRow.ValueS[‘version’]);
  case oRow.RowState of
    rsInserted: sMessage := ‘Inserted:  ‘ + sMessage;
    rsDeleted:  sMessage := ‘Deleted:  ‘ + sMessage;
    rsModified: sMessage := ‘Modified:  ‘ + sMessage;
  end;
  ShowMessage(sMessage);
end;

We also can view an original field value that was set either after the request or after the latest  CommitChanges 

mtTable.SourceView.Rows[0].GetData(‘version’, rvCurrent) //current value
mtTable.SourceView.Rows[0].GetData(‘version’, rvPrevious) //previous value;

Merger

On the TFDDataSet level, there is a documented method MergeDataSet and usually, it is enough. On the level of inner objects, it relies on TFDDatsTable.Merge that executes data merger. But what is important is that the merger will be correct if you have a primary key for your Table. It can be assigned by the creation of a unique index on the TFDDataSet level with the soPrimary parameter or it can be indicated in ProviderFlags – pfInKey of the fields or assigned by the created TFDDatSUniqueConstraint on the TFDDatsTable level.

  mtTable.Table.Constraints.AddUK(‘PK_CAPTION’, ‘Caption’, True);

Editing of the uneditable

Quite often it is necessary to change data in TFDDataSet that are readonly because of these or those reasons. Let’s have a look at a very simple example. Fields’ metadata are returned in the request but the field that should be updated in the request is calculated. It leads to setting readonly there. Fortunately, the TFDDataSet level provides all the necessary properties. There are a lot of settings that can forbid editing that’s why let’s use a single program interface.

  TUpdatableSave = record
DataSet: TFDDataSet;
CheckUpdatableSave: Boolean;
CheckReadOnlySave: Boolean;
EnforceConstraintsSave: Boolean;
TableCheckReadOnlySave: Boolean;
  end;

function ForceUpdatableBegin(ADataSet: TFDDataSet): TUpdatableSave;
begin
  Result.DataSet := ADataSet;
  Result.CheckUpdatableSave := TFDDataSetHack(Result.DataSet).UpdateOptions.CheckUpdatable;
  Result.CheckReadOnlySave := TFDDataSetHack(Result.DataSet).UpdateOptions.CheckReadOnly;
  Result.EnforceConstraintsSave := Result.DataSet.Table.EnforceConstraints;
  Result.TableCheckReadOnlySave := Result.DataSet.Table.CheckReadOnly;

  TFDDataSetHack(Result.DataSet).UpdateOptions.CheckUpdatable := False;
  TFDDataSetHack(Result.DataSet).UpdateOptions.CheckReadOnly := False;
  Result.DataSet.Table.EnforceConstraints := False;
  Result.DataSet.Table.CheckReadOnly := False;
end;

procedure ForceUpdatableEnd(const ASave: TUpdatableSave);
begin
  TFDDataSetHack(ASave.DataSet).UpdateOptions.CheckUpdatable := ASave.CheckUpdatableSave;
  TFDDataSetHack(ASave.DataSet).UpdateOptions.CheckReadOnly := ASave.CheckReadOnlySave;
  ASave.DataSet.Table.EnforceConstraints := ASave.EnforceConstraintsSave;
  ASave.DataSet.Table.CheckReadOnly := ASave.TableCheckReadOnlySave;
end;
 
Talk to us and get your project moving
Book a free consultation with a solution expert.
Name
This field is required
E-mail
Company web site
This field is required
Phone Number
This field is required