Skip to content

The first interactions with Data

If you have written code in DataFlex for Windows, you already know how data works. A table is registered in FILELIST.CFG. Its structure lives in a set of files on disk, and its .FD file hands the compiler the table and column numbers. Open gives you a Table.Column record buffer. A Data Dictionary class, built in the DD Modeler, puts rules on top of that, and Entry_Item Table.Column puts a control on the screen.

Almost all of that knowledge still applies. The TechStack keeps Data Dictionaries, DDO structures, Field_Option with its DD_ names, Request_Save, Main_DD, Server and Entry_Item. What it replaces is the three mechanisms underneath: the table registry, the record buffer, and the driver plumbing. Most new names on this page are old ideas that moved somewhere else.

This page starts with why they moved, then gives you a side-by-side list of the names, and only then walks the data layer step by step. The snippets are fragments meant to be read, not a project to build along with — the Conference App sample holds the complete, runnable version of everything shown here. Throughout, the example is a Room and a Presentation that takes place in it.

Why the data layer changed

Three properties of the Windows data model set the shape of everything built on it. Each one is the reason for one of the replacements.

The model lived outside the source

In DataFlex for Windows the table registry is a separate file. FILELIST.CFG links a table's DataFlex name, its filelist number, its user-display name and its physical name; the running program reaches tables by that number, and the compiler takes the number and the column constants from the table's .fd file.

That works, but the definition of your data is not in your source. It is in a registry file, a structure on disk and a generated include — three artifacts that have to agree with each other and with your code. The official documentation is blunt about the failure mode: if a program is run or compiled with different filelists that assign names or numbers differently, serious problems can arise.

In the TechStack the table definition is source. An Entity … End_Entity block carries the fields, the indexes, the relations and the metadata, so a data-model change is a diff you can review, and there is no second copy to keep in step.

One record buffer per table, for the whole program

Open creates a file buffer, and that buffer is global: there is exactly one instance of any Customer.Name in the application. Data Dictionary objects need per-object record state, so they were given local DDO-field buffers around the global buffer, plus synchronization between the two — which is why the Windows documentation has to explain when to use the file buffer and when to use the DDO-field buffer.

That is the second thing the TechStack removes. A Data Dictionary object's buffer is now the only record buffer there is; two objects on the same entity hold two independent records, and there is no shared Room.Name behind them. One buffer model, one place a value can come from — and one whole class of "who moved my buffer" bug that no longer has a mechanism to happen in.

The back end was chosen per table, outside the code

Reaching something other than the embedded database meant a licensed DataFlex SQL Driver and an INT file per table telling the runtime which driver and which server to use. Managed connections improved that by putting Connection IDs in one central place — and that is the idea the TechStack keeps.

What changes is where the driver comes from. A TechStack connection is a URI, and its scheme names a driver class registered in your application. A driver is ordinary source code written against a documented adapter protocol, so a new back end — another SQL engine, or a web service that should behave like a table — is a class someone can write, not a kit that has to ship. See Building Database Drivers.

Two consequences you will notice quickly. The application can create its own storage, because that is just another Data Dictionary operation. And the same model runs where paths mean different things: on WebAssembly the database file lives in the browser's persistent storage, and only the URI says so.

What you had, and what you have now

The first table is about defining data, the second about working with it. Read the left half as the part you already know.

Defining the data

DataFlex Windows What it did there TechStack What it is for
FILELIST.CFG entry plus the table's file set (.dat, .tag, .k##, .fd) Registered the table by name, number, display name and physical name, and stored its structure on disk Entity … End_Entity The table definition, in source, under version control with the code that uses it
Column attributes in the Table Editor Held type, length, key and auto-increment settings in the table structure { PrimaryKey=True AutoIncrement=True }, { Type=NVARCHAR, Length=100 } The same attributes, written next to the field they describe
Indexes in the table structure, one .k## file each Defined the orders you could find in Add_Index Name ASC The same indexes, declared inside the entity
Table relationships in the database definition, restated per class with Set Add_Server_File / Set Add_Client_File Paired parent and child columns, then declared the same structure again in each Data Dictionary class Add_Relation RoomId to Room.RoomId The relation, stated once, as part of the model
Room.File_Number, Field Customer.Id from the .fd file Turned names into the table and column numbers the runtime used RefEntity(Room), RefTable(Self.Title) Compile-time references to entity and field metadata
A Data Dictionary class with Set Main_File to Room.File_Number Attached the class to a table and gave it rules and events Class cRoomDataDictionary is a cDataDictionary with Set phEntity to (RefEntity(Room)) The same role, attached to an entity instead of a file number
A licensed SQL driver selected per table through its INT file Chose the back end table by table, outside application source cDbConnectionManager with a cDbSqliteDriver child, or RegisterDriver / RegisterConnection Registers driver classes and connections in source; the URI scheme picks the driver
Connection ID in cConnection Kept server and database strings in one central place psConnectionId with psConnectionUri The same central naming, now the only thing an entity says about storage
Tables made in the database tools, or at runtime with Make_File Storage had to exist before the application used it ExistsInDb, CreateDatabase The application checks for and creates its own storage through a Data Dictionary

Working with the data

DataFlex Windows What it did there TechStack What it is for
Customer.Name on the global file buffer, with DDO-field buffers layered over it One record buffer per opened table for the whole program, plus local DD copies that had to be synchronized oRoomDD.Name, or Self.Name inside the class Reads and writes the Data Dictionary object's own buffer — the only record buffer there is
DDO_Server Named the parent DDO and built the DDO structure Set DDO_Server to oRoomDD The same thing, and it fails when the entity has no relation to that parent
Relates-to constraints Limited a child to the active parent record Set Constrain_File … with RebuildConstraints Unchanged in purpose and in shape
Set Field_Option Field Customer.Id DD_Required to True (Field_Option settings) Declared field rules in the Data Dictionary class Set Field_Option (RefTable(Self.Title)) DD_Required to True The same property and the same DD_ names; only the field reference changed
Foreign-field attributes with DD_KEYFIELD / DD_INDEXFIELD / DD_DEFAULT Applied rules to a DDO acting as a parent Set Foreign_Field_Option … DD_KEYFIELD DD_FindReq to True Same categories, same purpose
A procedure attached with Field_Validate_msg (field validation events) Ran your own check for one field, from inside the DD class cDD_Field_Validator with a Defined option ID A rule of your own as a reusable object, switched on per field like a built-in option
Augmenting the Validate_Save and Validate_Delete Data Dictionary events Whole-record validation by subclassing and forwarding OnValidate_Add, OnFieldValidate_Add and their _Del / _Has counterparts The same checks, registered as handlers instead of overridden
Request_Save, Request_Delete Ran the DD's rules, then wrote to the database Request_Validate, Request_Save, Request_Delete The same entry points, on the Data Dictionary object
Clear, Find, SaveRecord, ZeroFile against a table Command-style code operating on the table's global buffer Clear oDD, Find EQ oDD.Field, SaveRecord oDD, ZeroFile oDD Identical commands; the operand is the Data Dictionary object that owns the buffer
Transactions with DDOs Grouped database work into one unit Begin_Transaction / End_Transaction Unchanged; Data Dictionary operations take part
Entry_Item Customer.Name, with Main_DD and Server Bound a control to a column of the global table buffer, in a view served by a DDO Entry_Item oPresentationDD.Title, with Main_DD and Server Binds a control to a field in a Data Dictionary object's buffer

The condensed versions of these mappings live with the reference pages: Entities for the model, Data Binding for the UI, and Command API for command-style code.

How to read the rest of this page

Eight steps follow, in the order a developer meets them. Each one introduces a single idea, shows the smallest fragment that demonstrates it, and closes with a note on what you would have written in DataFlex for Windows.

  1. Name the database
  2. Declare the data model
  3. Wrap an entity in a Data Dictionary
  4. Add business rules
  5. Connect Data Dictionary objects
  6. Write business procedures
  7. Synchronize from a service
  8. Bind to controls

Step 1 — Name the database

A database is addressed by URI, and the model refers to a logical connection ID rather than to a path or a driver.

Object oConnections is a cDbConnectionManager
    Object oSqliteConn is a cDbSqliteDriver
        Set psConnectionId to "first_data"
        Set psConnectionUri to "sqlite:/dev/idbfs/FirstData.db?mode=rwc"
    End_Object
End_Object

The URI scheme selects the driver, so the same model reaches a different backend by changing one string. mode=rwc opens the database read/write and creates the file when it is missing. first_data is the name the model will use, and that indirection is what lets the file move without touching an entity. On WebAssembly a path under /dev/idbfs/ lives in the browser's persistent storage, so the database survives a reload. Nothing here opens anything — the connection is established on first use.

The same thing can be written without a driver object, by registering the driver name and the connection separately:

Object oConnections is a cDbConnectionManager
    Send RegisterDriver "sqlite" (RefClass(cDbSqliteDriver))
    Send RegisterConnection "first_data" "sqlite:/dev/idbfs/FirstData.db?mode=rwc"
End_Object

This notation says out loud what the URI means: RegisterDriver binds the name sqlite to a driver class, and that name is exactly the scheme the URI starts with — so RegisterConnection pairs a connection ID with a URI whose first segment is a lookup into the drivers registered above. It stores both without instantiating the driver or connecting. The two notations are equivalent; the child-object form is shorthand in which the object's class supplies the driver and its properties supply the pair.

See Connections, cDbConnectionManager and cDbSqliteDriver.

In DataFlex Windows

The same decisions were spread over the workspace and the tools: the Filelist: and Data: entries in the workspace configuration said where tables were found, and a table's INT file said which SQL driver to use for it. A Connection ID held by cConnection is the direct ancestor of psConnectionId — same reason, same benefit at deployment.

Step 2 — Declare the data model

The table definition lives in source, and a relation is part of the model, stated once.

{ Connection=first_data }
Entity Room
    { PrimaryKey=True AutoIncrement=True }
    Integer RoomId
    String Name
    Integer Capacity

    Add_Index RoomId ASC
    Add_Index Name ASC
End_Entity
{ Connection=first_data }
Entity Presentation
    { PrimaryKey=True AutoIncrement=True }
    Integer PresentationId
    Integer RoomId
    String Title
    Date Date

    Add_Index PresentationId ASC
    Add_Index RoomId ASC PresentationId ASC

    Add_Relation RoomId to Room.RoomId
End_Entity

The annotations in braces carry the metadata that used to live in the .FD file — the primary key, auto-increment, and optionally the physical type with { Type=NVARCHAR, Length=100 }. The { Connection=… } annotation is the only place the model mentions where it is stored.

Add_Relation names the parent entity, so the parent must already be declared when the compiler reads it. The relation is a model fact; step 5 is where two objects are connected using it.

Entity types double as struct types, which is what makes the typed JSON in step 7 work without a separate struct declaration.

See Entities and Relations and Constraints.

In DataFlex Windows

This is the step with the fewest survivors. The table was created and edited in the Table Editor, stored as a file set whose .fd member fed the compiler, registered in FILELIST.CFG with a filelist number, and related to other tables in the database definition. All of that now lives in the Entity block.

Step 3 — Wrap an entity in a Data Dictionary

The buffer moved, and this is the step where that becomes concrete: a Data Dictionary class attaches behavior to an entity, and every object of that class owns its own record.

{ Entity=Room }
Class cRoomDataDictionary is a cDataDictionary
    Procedure Construct_Object
        Forward Send Construct_Object

        Set phEntity to (RefEntity(Room))
    End_Procedure
End_Class

The class attaches behavior to an entity, and phEntity is that attachment. Inside the class, Self.Name refers to the object's own buffer field. Two objects of this class can sit on two different rows of Room at the same time, which is the change that removes the old "who moved my buffer" class of bug.

The class is a normal class, so it is also the natural home for behavior belonging to that table — step 6 uses that. This page writes one class per entity, and a Data Dictionary object is created per view or per business object.

See Data Dictionaries and cDataDictionary.

In DataFlex Windows

A Data Dictionary class looked almost exactly like this, with Set Main_File to Room.File_Number where Set phEntity to (RefEntity(Room)) now stands, and with Set Add_Server_File / Set Add_Client_File lines declaring the structure (Construct_Object). Those structure lines are gone, because the entity already holds the relation and the parent is chosen per object in step 5.

Step 4 — Add business rules

Rules are declared on fields, in the Data Dictionary, and they run before a save regardless of who triggered it — the UI, a business procedure or a synchronization run.

Set Field_Option (RefTable(Self.Title)) DD_Required to True
Set Field_Option (RefTable(Self.RoomId)) DD_Required to True
Set Field_Option (RefTable(Self.Date)) DD_Required to True

These lines sit in the class's Construct_Object, and RefTable resolves the field to the handle the option applies to. Besides DD_Required, the built-in options include DD_FindReq, DD_Capslock, DD_NoEnter, DD_NoPut, DD_DisplayOnly and DD_Commit; see Validations and Field_Option.

A rule of your own is an option too — a cDD_Field_Validator object with an ID:

Define DD_MinimumTitle for "CUSTOM_MINIMUMTITLE"

Object oTitleValidator is a cDD_Field_Validator
    Set psOptionId to DD_MinimumTitle

    Procedure Validate_DD_Field tDD_OnFieldValidate ByRef details Boolean ByRef bCancel
        String sValue

        Get Field_Current_Value of details.hoDD details.hField to sValue

        If (Trim(sValue) <> "" and Length(Trim(sValue)) < 3) Begin
            Send FieldError of details.hoDD details.hField DFERR_OPERATOR "A title needs at least three characters"
            Move True to details.bInvalid
        End
    End_Procedure
End_Object

One line switches it on, next to the DD_Required lines above:

Set Field_Option (RefTable(Self.Title)) DD_MinimumTitle to True

The validator reads the value from details.hoDD and details.hField, never from a global buffer — that is what makes one validator object reusable across Data Dictionary classes. It ignores an empty value because DD_Required already owns that case, so each rule keeps one job. A failure calls FieldError and sets details.bInvalid.

Cross-field rules need the whole record rather than one field, so they use the DD-level OnValidate_Add, OnValidate_Del and OnValidate_Has events instead.

In DataFlex Windows

Same property, same constants, different field reference: Set Field_Option Field Presentation.Title DD_Required to True. The Field_Option settings page is still the catalogue of what each DD_ name does. Your own rules were procedures attached with Field_Validate_msg (field validation events) or checks inside the Validate_Save event (Data Dictionary events). The validator object here is the reusable form of the first; the OnValidate_* events are the registration-based form of the second.

Step 5 — Connect Data Dictionary objects

The relation declared in step 2 is a model fact; connecting two objects is a separate, explicit act.

Object oRoomDD is a cRoomDataDictionary
End_Object

Object oPresentationDD is a cPresentationDataDictionary
    Set DDO_Server to oRoomDD
End_Object

DDO_Server makes oRoomDD the parent of oPresentationDD, so whenever the child is positioned the parent buffer follows to the related room — no join, no lookup code. The relation must already exist on the entity, or there is nothing to follow.

A child can additionally be limited to the parent's current row with Constrain_File and RebuildConstraints; see Relations and Constraints.

Creating the storage is a Data Dictionary operation too:

Get ExistsInDb of oPresentationDD True True to bExists
If (not(bExists)) Begin
    Send CreateDatabase of oPresentationDD True True True
End

Because the call travels the object graph, one CreateDatabase on the child covers its parents too. ExistsInDb also initializes the connection and opens the database file, which is where the URI from step 1 is finally used. No statement here mentions SQLite — the connection manager picks the driver from the sqlite: scheme.

In DataFlex Windows

DDO_Server and relates-to constraints are the properties you already use. What is new is where the relation comes from — the entity, not the database definition — and that the Data Dictionary can create the storage itself, where before the tables existed beforehand or were made at runtime with Make_File.

Step 6 — Write business procedures

Reading and writing is a cycle on the object's own buffer — put a value in, find, read or clear, save — and because a Data Dictionary is a class, that cycle belongs inside it.

A Settings entity of two fields, Name and Value, is enough to show it; the declaration adds nothing new after step 2. Its Data Dictionary class carries the pair of accessors:

Function LoadSetting String sSetting String sDefault Returns String
    Move sSetting to Self.Name
    Send FindByField EQ (RefTable(Self.Name))

    If (Found) ;
        Function_Return Self.Value
    Else ;
        Function_Return sDefault
End_Function
Procedure StoreSetting String sSetting String sValue
    Move sSetting to Self.Name
    Send FindByField EQ (RefTable(Self.Name))

    If (not(Found)) Begin
        Send Clear
        Move sSetting to Self.Name
    End

    Move sValue to Self.Value
    Send Request_Save
End_Procedure

Move sSetting to Self.Name writes the object's own buffer field. FindByField EQ searches on that field and sets Found. Send Clear starts a new record, and the key has to be written again afterwards because clearing empties the buffer. Request_Save runs the rules from step 4 first, so an invalid record never reaches the database.

The command-style equivalents — Clear oDD, Find EQ oDD.Field, SaveRecord oDD — read the same, but they take the Data Dictionary object where DataFlex Windows took a table. See Command API and Find.

In DataFlex Windows

The cycle is the one you know, and Clear, Find and SaveRecord still spell it. The difference is the operand: Clear Settings and Find EQ Settings.Name became Clear oDD and Find EQ oDD.Name, because the buffer belongs to the object. That is also why this code can live inside the Data Dictionary class and address Self.Name.

Step 7 — Synchronize from a service

The Conference App is built around one pattern: fetch typed JSON, then replace the local data in one transaction, validating each record through its Data Dictionary before saving it.

The fetch deserializes straight into the entity type:

Room[] aRooms
HttpStatus eStatus

Get HttpGet of oHttpReq "/ConferenceData/API/v1/Room" to eStatus
If (eStatus <> 200) Begin
    Send UserError (SFormat("Could not connect to server (HTTP status %1)", eStatus))
    Procedure_Return
End

Get HttpResponseToDataType of oHttpReq False "" to aRooms

The entity from step 2 is the deserialization target, so no separate struct is needed. The early return means a failed request leaves the existing local data untouched: fetch everything before changing anything.

The replacement runs as one transaction:

Begin_Transaction
    ZeroFile oRoomDD

    Move (SizeOfArray(aRooms) - 1) to iItemTo
    For iItem from 0 to iItemTo
        Clear oRoomDD
        Send UpdateAllFields of oRoomDD aRooms[iItem]
        Get Request_Validate of oRoomDD to bErr
        If (not(bErr)) Begin
            Send Request_Save of oRoomDD
        End
    Loop
End_Transaction

ZeroFile empties the table through the Data Dictionary and sits inside the transaction, so a failure leaves the previous contents intact. Clear followed by UpdateAllFields copies one struct into the buffer. Request_Validate returns True when the record is invalid, which is why the save is guarded — one bad row from the service is skipped instead of aborting the run. The whole download commits as one database operation. See Transactions, cHttpClient and HttpResponseToDataType.

A child record is validated against its parent's buffer, so before saving a presentation the room buffer is moved to the related row:

Move aPresentations[iItem].RoomId to oRoomDD.RoomId
Find EQ oRoomDD.RoomId

That is the same DDO_Server connection from step 5, now driven from code instead of from the UI.

Two closing notes. The service sends ISO-8601 dates and times, so the parsing is wrapped in Push_locale / Set_Attribute DF_LOCALE to DF_LOCALE_ISO8601 / Pop_locale (see Push_Locale). And a version marker stored through the business procedures of step 6 lets the routine skip the download when nothing changed. Synchronize data carries the complete procedure, including the cross-origin host configuration.

In DataFlex Windows

ZeroFile and transactions behave as they always did. The new part is that the entity doubles as the struct the JSON is deserialized into, so there is no separate struct declaration to keep in step with the table.

Step 8 — Bind to controls

After seven steps of change, binding is the part that barely moved. A control still names a field with Entry_Item; only the prefix is different.

DataFlex Windows TechStack
Entry_Item Presentation.Title Entry_Item oPresentationDD.Title
Prefix is the table, whose buffer is global Prefix is the Data Dictionary object, whose buffer is its own
Two objects on one table share one buffer Each Data Dictionary object holds its own record

A view names its main Data Dictionary object and then binds controls to fields:

Set Main_DD to oPresentationDD
Set Server to oPresentationDD

Object oPresentation_Title is a cWebForm
    Entry_Item oPresentationDD.Title
    Set psLabel to "Title"
End_Object

Object oRoom_Name is a cWebForm
    Entry_Item oRoomDD.Name
    Set psLabel to "Room"
End_Object

Main_DD and Server still name the view's main Data Dictionary object, exactly as they named a DDO in DataFlex Windows. The second control binds to a different Data Dictionary object in the same view and shows the matching room, because of DDO_Server. Save and delete actions still send Request_Save and Request_Delete to the view's main Data Dictionary object, which runs the rules from step 4.

The practical migration rule is one line: replace the table prefix with the name of the Data Dictionary object that owns the buffer. See Data Binding and Entry_Item.

In DataFlex Windows

Entry_Item took a table.column — the global buffer — while Main_DD and Server already named Data Dictionary objects. That mismatch is what disappears: all three now name the object that owns the buffer.

Things you will look for and not find

What you are looking for What to use instead
FILELIST.CFG, filelist numbers, the Data: search path The Entity block and its { Connection=… } attribute. In a TechStack workspace the FileList-based database tools are hidden and FileList tables no longer appear in the Table Explorer — see the release notes.
.fd files and the Table.File_Number / Field Table.Column constants RefEntity(Room) and RefTable(Self.Title)
Open Room, followed by Room.Name The Data Dictionary object's buffer: oRoomDD.Name, or Self.Name inside the class. There is no global table buffer to open.
Set Main_File to Room.File_Number Set phEntity to (RefEntity(Room)). Main_File still exists as a compatibility accessor, but entity references are the model.
Set Add_Server_File / Set Add_Client_File in the Data Dictionary class Add_Relation on the entity, then Set DDO_Server per object
Standalone Relate Nothing to call: entity relations plus Set DDO_Server maintain relationship state — see Relate in the Command API
An INT file, or a driver chosen per table One connection URI whose scheme selects the driver

Where to go next