• Blog
  • Description of low-level FireDac objects in TDataSet

Description of low-level FireDac objects in TDataSet

Data editing

Publish date:
Discover more of what matters to you

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

1234567891011121314151617181920212223
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

1234567
//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.

1234567891011121314151617181920
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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
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.

12345678910111213
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 

12
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.

1
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.

1234567891011121314151617181920212223242526272829
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;

Subscribe to our newsletter and get amazing content right in your inbox.

This field is required
This field is required Invalid email address

Thank you for subscribing!
See you soon... in your inbox!

confirm your subscription, make sure to check your promotions/spam folder

Subscribe to our newsletter and get amazing content right in your inbox.

You can unsubscribe from the newsletter at any time

This field is required
This field is required Invalid email address

You're almost there...

A confirmation was sent to your email

confirm your subscription, make sure to check
your promotions/spam folder