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