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

Description of low-level FireDac objects in TDataSet

Description of low-level FireDac objects in TDataSet

Publish date: 31.05.2022

    DISCOVER MORE OF WHAT MATTERS TO YOU

    Description of low-level FireDac objects in TDataSet

    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

    Tags

    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