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.
TDataSet = class(TComponent, IProviderSupport) ... public // 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;
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); var strStatus: string; begin case cds.State of dsBrowse: strStatus := 'Browse'; dsEdit: strStatus := 'Edit'; dsInsert: strStatus := 'Insert'; else strStatus := 'Other state'; end; StatusBar.Panels.Text := strStatus; end;
The code considers only the three most common states of a dataset component, ignoring the inactive state and other special cases.