The DataSet Component

The DataSet Component

Instead of proceeding with the discussion of the capabilities of a specific dataset at this point, I prefer to devote some space to a generic introduction of the features of the TDataSet class, which are shared by all inherited data-access classes. The DataSet component is very complex, so I won't list all its capabilities—I will only discuss its core elements.

This component provides access to a series of records that are read from some source of data, kept in internal buffers (for performance reasons), and eventually modified by a user, with the possibility of writing back changes to the persistent storage. This approach is generic enough to be applied to different types of data (even non-database data), but it has a few rules:

  • There can be only one active record at a time, so if you need to access data in multiple records, you must move to each of them, read the data, then move again, and so on. You'll find an example of this and related techniques in the section "Navigating a Dataset."

  • You can edit only the active record: You cannot modify a set of records at the same time, as you can in a relational database.

  • You can modify data in the active buffer only after you explicitly declare you want to do so, by giving the Edit command to the dataset. You can also use the Insert command to create a new blank record and close both operations (insert or edit) by giving a Post command.

Other interesting elements of a dataset that I'll explore in the following sections are its status (and the status change events), navigation and record positions, and the role of field objects. As a summary of the capabilities of the DataSet component, I included the public methods of its class in Listing 13.2 (the code has been edited and commented for clarity). Not all of these methods are directly used everyday, but I kept them all in the listing.

Listing 13.2: The Public Interface of the TDataSet Class (Excerpted)
Start example
TDataSet = class(TComponent, IProviderSupport)
  // create and destroy, open and close
  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;
 procedure Open;
 procedure Close;
 property BeforeOpen: TDataSetNotifyEvent read FBeforeOpen write FBeforeOpen;
  property AfterOpen: TDataSetNotifyEvent read FAfterOpen write FAfterOpen;
  property BeforeClose: TDataSetNotifyEvent
    read FBeforeClose write FBeforeClose;
  property AfterClose: TDataSetNotifyEvent read FAfterClose write FAfterClose;
  // status information
 function IsEmpty: Boolean;
 property Active: Boolean read GetActive write SetActive default False;
 property State: TDataSetState read FState;
 function ActiveBuffer: PChar;
 property IsUniDirectional: Boolean
    read FIsUniDirectional write FIsUniDirectional default False;
 function UpdateStatus: TUpdateStatus; virtual;
 property RecordSize: Word read GetRecordSize;
 property ObjectView: Boolean read FObjectView write SetObjectView;
 property RecordCount: Integer read GetRecordCount;
 function IsSequenced: Boolean; virtual;
 function IsLinkedTo(DataSource: TDataSource): Boolean;
  // datasource
 property DataSource: TDataSource read GetDataSource;
 procedure DisableControls;
 procedure EnableControls;
 function ControlsDisabled: Boolean;
  // fields, including blobs, details, calculated, and more
 function FieldByName(const FieldName: string): TField;
  function FindField(const FieldName: string): TField;
 procedure GetFieldList(List: TList; const FieldNames: string);
  procedure GetFieldNames(List: TStrings); virtual; // virtual since Delphi 7
 property FieldCount: Integer read GetFieldCount;
  property FieldDefs: TFieldDefs read FFieldDefs write SetFieldDefs;
  property FieldDefList: TFieldDefList read FFieldDefList;
  property Fields: TFields read FFields;
  property FieldList: TFieldList read FFieldList;
  property FieldValues[const FieldName: string]: Variant
    read GetFieldValue write SetFieldValue; default;
 property AggFields: TFields read FAggFields;
 property DataSetField: TDataSetField
    read FDataSetField write SetDataSetField;
 property DefaultFields: Boolean read FDefaultFields;
 procedure ClearFields;
 function GetBlobFieldData(FieldNo: Integer;
    var Buffer: TBlobByteData): Integer; virtual;
 function CreateBlobStream(Field: TField;
    Mode: TBlobStreamMode): TStream; virtual;
  function GetFieldData(Field: TField;
    Buffer: Pointer): Boolean; overload; virtual;
 procedure GetDetailDataSets(List: TList); virtual;
  procedure GetDetailLinkFields(MasterFields, DetailFields: TList); virtual;
  function GetFieldData(FieldNo: Integer;
    Buffer: Pointer): Boolean; overload; virtual;
  function GetFieldData(Field: TField; Buffer: Pointer; NativeFormat: Boolean):
    Boolean; overload; virtual;
 property AutoCalcFields: Boolean
    read FAutoCalcFields write FAutoCalcFields default True;
 property OnCalcFields: TDataSetNotifyEvent
    read FOnCalcFields write FOnCalcFields;
  // position, movement
 procedure CheckBrowseMode;
 procedure First;
 procedure Last;
 procedure Next;
 procedure Prior;
 function MoveBy(Distance: Integer): Integer;
 property RecNo: Integer read GetRecNo write SetRecNo;
 property Bof: Boolean read FBOF;
 property Eof: Boolean read FEOF;
 procedure CursorPosChanged;
 property BeforeScroll: TDataSetNotifyEvent
    read FBeforeScroll write FBeforeScroll;
  property AfterScroll: TDataSetNotifyEvent
    read FAfterScroll write FAfterScroll;
  // bookmarks
  procedure FreeBookmark(Bookmark: TBookmark); virtual;
 function GetBookmark: TBookmark; virtual;
 function BookmarkValid(Bookmark: TBookmark): Boolean; virtual;
 procedure GotoBookmark(Bookmark: TBookmark);
 function CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer; virtual;
 property Bookmark: TBookmarkStr read GetBookmarkStr write SetBookmarkStr;
  // find, locate
 function FindFirst: Boolean;
  function FindLast: Boolean;
  function FindNext: Boolean;
  function FindPrior: Boolean;
 property Found: Boolean read GetFound;
 function Locate(const KeyFields: string; const KeyValues: Variant;
    Options: TLocateOptions): Boolean; virtual;
  function Lookup(const KeyFields: string; const KeyValues: Variant;
    const ResultFields: string): Variant; virtual;
  // filtering
 property Filter: string read FFilterText write SetFilterText;
  property Filtered: Boolean read FFiltered write SetFiltered default False;
  property FilterOptions: TFilterOptions
    read FFilterOptions write SetFilterOptions default [];
 property OnFilterRecord: TFilterRecordEvent
    read FOnFilterRecord write SetOnFilterRecord;
  // refreshing, updating
 procedure Refresh;
 property BeforeRefresh: TDataSetNotifyEvent
    read FBeforeRefresh write FBeforeRefresh;
  property AfterRefresh: TDataSetNotifyEvent
    read FAfterRefresh write FAfterRefresh;
 procedure UpdateCursorPos;
 procedure UpdateRecord;
 function GetCurrentRecord(Buffer: PChar): Boolean; virtual;
 procedure Resync(Mode: TResyncMode); virtual;
  // editing, inserting, posting, and deleting
 property CanModify: Boolean read GetCanModify;
 property Modified: Boolean read FModified;
 procedure Append;
 procedure Edit;
 procedure Insert;
 procedure Cancel; virtual;
 procedure Delete;
 procedure Post; virtual;
  procedure AppendRecord(const Values: array of const);
 procedure InsertRecord(const Values: array of const);
 procedure SetFields(const Values: array of const);
  // events related to editing, inserting, posting, and deleting
 property BeforeInsert: TDataSetNotifyEvent
    read FBeforeInsert write FBeforeInsert;
  property AfterInsert: TDataSetNotifyEvent
    read FAfterInsert write FAfterInsert;
  property BeforeEdit: TDataSetNotifyEvent read FBeforeEdit write FBeforeEdit;
  property AfterEdit: TDataSetNotifyEvent read FAfterEdit write FAfterEdit;
  property BeforePost: TDataSetNotifyEvent read FBeforePost write FBeforePost;
  property AfterPost: TDataSetNotifyEvent read FAfterPost write FAfterPost;
  property BeforeCancel: TDataSetNotifyEvent
    read FBeforeCancel write FBeforeCancel;
  property AfterCancel: TDataSetNotifyEvent
    read FAfterCancel write FAfterCancel;
  property BeforeDelete: TDataSetNotifyEvent
    read FBeforeDelete write FBeforeDelete;
  property AfterDelete: TDataSetNotifyEvent
    read FAfterDelete write FAfterDelete;
 property OnDeleteError: TDataSetErrorEvent
    read FOnDeleteError write FOnDeleteError;
  property OnEditError: TDataSetErrorEvent
    read FOnEditError write FOnEditError;
 property OnNewRecord: TDataSetNotifyEvent
    read FOnNewRecord write FOnNewRecord;
  property OnPostError: TDataSetErrorEvent
    read FOnPostError write FOnPostError;
  // support utilities
 function Translate(Src, Dest: PChar;
    ToOem: Boolean): Integer; virtual;
 property Designer: TDataSetDesigner read FDesigner;
 property BlockReadSize: Integer read FBlockReadSize write SetBlockReadSize;
 property SparseArrays: Boolean read FSparseArrays write SetSparseArrays;
End example

The Status of a Dataset

When you operate on a dataset in Delphi, you can work in different states. These states are indicated by a specific State property, which can assume several different values:

dsBrowse  Indicates that the dataset is in normal browse mode; used to look at the data and scan the records.

dsEdit  Indicates that the dataset is in edit mode. A dataset enters this state when the program calls the Edit method or the DataSource has the AutoEdit property set to True, and the user begins editing a data-aware control, such as a DBGrid or DBEdit. When the changed record is posted, the dataset exits the dsEdit state.

dsInsert  Indicates that a new record is being added to the dataset. This might happen when calling the Insert or Append methods, moving to the last line of a DBGrid, or using the corresponding command of the DBNavigator component.

dsInactive  Indicates a closed dataset.

dsCalcFields  Indicates that a field calculation is taking place (during a call to an OnCalcFields event handler).

dsNewValue, dsOldValue, and dsCurValue  Indicate that an update of the cache is in progress.

dsFilter  Indicates that a dataset is setting a filter (during a call to an OnFilterRecord event handler).

In simple examples, the transitions between these states are handled automatically, but it is important to understand them because many events refer to the state transitions. For example, every dataset fires events before and after any state change. When a program requests an Edit operation, the component fires the BeforeEdit event just before entering edit mode (an operation you can stop by raising an exception). Immediately after entering edit mode, the dataset receives the AfterEdit event. After the user has finished editing and requests to store the data by executing the Post command, the dataset fires a BeforePost event (which can be used to check the input before sending the data to the database); it fires an AfterPost event after the operation has been successfully completed.

Another more general state-change tracking technique involves handling the DataSource component's OnStateChange event. As an example, you can show the current status with code like this:

procedure TForm1.DataSource1StateChange(Sender: TObject);
  strStatus: string;
  case cds.State of
    dsBrowse: strStatus := 'Browse';
    dsEdit: strStatus := 'Edit';
    dsInsert: strStatus := 'Insert';
    strStatus := 'Other state';
  StatusBar.Panels[0].Text := strStatus;

The code considers only the three most common states of a dataset component, ignoring the inactive state and other special cases.

Part I: Foundations