Basic Data Dictionary and Table Concepts

  • Tables, Columns and Rows

  • Indexes

  • Relationships

  • Record Identity and RowId

  • Transactions, Locking and Multi-user Support

  • File Buffers and DDO-Field Buffers

  • The File_Field and Field Keywords

  • The File Commands

  • The Database API


Tables, Columns and Rows

Databases are usually defined as a collection of tables. Tables consist of a set of named columns of a specific data-type and length. The data in the tables are called rows.

The interface message names of Data Dictionaries use the following terminology to describe databases:

  • File – A File refers to a table.

  • Field – A Field refers to a table’s column.

  • Record – A Record refers to a row of data from a single table

The Data Dictionary messages use File, Field and Record in their interface names. Some examples of this are Main_File, Field_Options, File_Field_Current_Value, and OnNewCurrentRecord.

While the documentation will use some of these terms interchangeably, the normal use of terms will be:

  • Table – Table is used when referring to database tables. You should only see the word “file” in the actual interface messages.

  • Field – Field is used when referring to table-columns and is used when referring to the entity in the Data Dictionary that defines a column. A Data Dictionary maintains structures of information about each table’s column such as values, labels, and options (Field_Current_Value, Field_Label, Field_Options). These will be referred to as fields within the Data Dictionary.

  • Record – Record is used to refer to a row of data from a table.

See Also

Basic Data Dictionary and Table Concepts


Indexes

All record finding in Data Dictionaries occur using indexes. Indexes are used to quickly find individual records and are used to quickly find up or down a table in a specified order.

To be used properly by Data Dictionaries each index entry must be unique. In other words, the segments used to create an index must not allow duplicates. Uniqueness is usually assured by appending the primary key field(s) as the last segment(s) of the index.

Indexes are defined within Database Builder and assigned an index number. That number is used in Data Dictionaries and in your code to determine which index to use.

See Also

Basic Data Dictionary and Table Concepts


Relationships

Relationships are used to "Normalize" your data. Some of the goals of normalization are…

  1. To Eliminate Repeating Groups - Make a separate lookup table for each set of related attributes, and give each table a primary key. For example, you may be recording contacts that you have with your clients. You should not store the contacts in the client table. Instead you would move the contact information into a separate table and relate each record to the primary key in the Client file.

  2. To Eliminate Redundant Data - If an attribute depends on only part of a multi-valued key, remove it to a separate table. Let’s say each contact you have with a client is categorized (Telephone call, mail-out, personal visit, etc.). You should store the Contact-Types in a separate table and relate the Contacts to the contact-types.

  3. To Eliminate Columns Not Dependent On Key - If attributes do not contribute to a description of the key, remove them to a separate table. For example, suppose you are storing the Client's Employer Name, Employer Address and Employer Phone Number. These attributes describe the client's place of work, not the client, so you should create an Employer table and remove the Employer information to this table relating to its Key from the customer table.

These are the first three forms of data normalization and are probably the ones you will apply most-commonly to your database.

Relationships are represented in Visual DataFlex using the standard foreign-key and primary-key model.

  1. A relationship must be defined from a child to a parent table. A child table defines a field or set fields that refer a corresponding set of fields in the parent. The size and data type of fields in the parent and child must be the same. This relationship is defined using Database Builder.

  2. The value of the related-to field(s) in the parent must be unique and supported by an index whose segment(s) are the same as the related to field. The related-to field in the parent is almost always your primary key and is referred to in Data Dictionaries as the key field.

  3. The child table will usually contain one or more indexes that allow for fast finding by the relating fields. This means that the first segment(s) in this child should consist of the relating-to field(s).

Data Dictionaries make use of relationships in four ways:

  1. Relates: When a Data Dictionary finds a record, all parent DDOs will automatically find their related-to parent records. The parent DDO of those DDOs will then find their related-to records on up the relationship structure. The find/relate process finds an entire structure of related records.

  2. Attaches: Before a save or a find, the values of the parent related-to fields are moved into child relating fields. This attach process, makes sure that child-parent relationships are properly maintained during saves.

  3. Constraints: A relates-to constraint defined in a Data Dictionary Structure constrains the finding of child records to a parent. This feature is used extensively in header-detail types of applications (e.g., an order entry system where order-details records should be constrained a specific order).

  4. Validations and Save: A validation occurs before a save validation occurs in the Main DDO and all related parent DDOs. In addition, the relational DDO structure is inspected before the save. If the entire structure is not in place, validation will fail. When a save occurs the record in the main DDO and all of the records in the parent DDO are saved as a single transaction.

Basically, relationships in Data Dictionaries allow you to work with a hierarchy of records as a single entity.

See Also

Basic Data Dictionary and Table Concepts


Record Identity and RowId

Every table needs an identifier that uniquely identifies a record. This identifier is used to find and re-find records. This is done frequently and therefore the identifier must provide the fastest way possible to find a record.

Some databases have a built in record identifier in every table. The DataFlex embedded database and the Pervasive.SQL both auto assign unique numeric identifiers to each record. These identifiers, referred to as recnum, provide the fastest way to find a single record.

Most SQL databases such as Microsoft’s SQL Server and IBM’s DB2 do not provide a built in record identity. Instead, tables in these databases almost always contain primary keys and an index that provides fast access to any record via that Primary Key. The Primary Key can be defined as a single column or as a combination of columns. The data-type of the primary key column(s) may be of any type.

If your database supports recnum, you can use recnum-based commands and Data Dictionary messages to identify and find records.

// Low level commands using recnum

Integer iTempRec

Move Customer.Recnum to iTempRec

Clear Customer

:

Move iTempRecnum to Customer.Recnum

Find EQ Customer by recnum

// Data Dictionary methods using recnum

Integer iTempRec

Integer iFile

Get Current_Record of hoDDO to iTempRec

Send Clear to hoDDO

:

Get Main_File of hoDDO to iFile

Send Find_by_Recnum of hoDDO iFile iTempRec

If your database uses a Primary Key as its unique identify, you can use other commands and Data Dictionary messages to identify and find records.

// low level commands using primary key

String sTempId

Move Customer.Customer_Id to sTempId

Clear Customer

:

Move sTempId to Customer.Customer_Id

Find EQ Customer by 1 // find EQ by index 1

// Data Dictionary methods using primary key

String sTempId

Get Field_Current_Value of hoDDO Field Customer.Customer_Id to sTempId

Send Clear to hoDDO

:

Move sTempId to Customer.Customer_Id

Send Find of hoDDO EQ 1 // find EQ mode and index 1

The above example assumes that the Customer Table’s primary key is Customer.Customer_Id and its index is index 1. Each table will have a different definition for its primary key and primary key index. Depending you your table you need to change the above code to support different Primary Key different field(s) names, different Primary Key Indexes and different Primary key data-types.

There is a major disadvantage with the above methods. A different syntax is required for different databases and in some cases, different syntax for each table. This means it is very difficult to write abstracted code that can be applied to any table in any database. This kind of abstraction is a goal and requirement of Data Dictionary programming.

This is solved by introducing RowIds.

The record identify of every table is mapped to a special data-type called RowId. You define the record-identity to RowId mapping when you define your table in Database Builder. Once mapped, a set of RowId commands, functions and Data Dictionary methods are used to identify and find records. This allows the same syntax to be used for any table.

// Low level commands using RowId

RowId riTempId

Boolean bFound

Move (GetRowId(Customer.File_Number)) to riTempId

Clear Customer

:

Move (FindByRowId(Customer.File_Number,riTempId)) to bFound

// Data Dictionary methods using RowId

RowId riTempId

Get CurrentRowId of hoDDO to riTempId

Send Clear to hoDDO

:

Send FindByRowId of hoDDO riTempId

By using this RowId syntax, you now have a single syntax that can be used with any table from any database. Once defined, you just program using RowId.

The RowId Data Type

RowId riTempId1 riTempId2

:

Property RowId priLastId

:

Get priLastId to riTempId1

Move roTempId1 to riTempId2

Set priLastId to riTempId2

A special data-type type named RowId is used to store RowId values. This data-type has a restricted set of behaviors. Because the underlying data-type can be any type or any combination of types, it cannot be cast to any other data-type or directly used to perform any kind of evaluation. Instead a set of RowId global functions and Data Dictionary Methods are provided that allow you to perform all required RowId manipulations.

The RowId Global Functions

The following functions provide low level RowId support:

FindByRowId | Move (FindByRowId(iFile,riRowId)) to bFound
GetRowId | Move (GetRowId(iFile)) to riRowId
NullRowId | Move (NullRowId()) to riRowId
IsNullRowId | Move (IsNullRowId(riRowId)) to bIsNull
IsSameRowId | Move (IsSameRowId(riRowId1,riRowId2)) to bIsSame
SerializeRowId | Move (SerializeRowId(riRowId)) to sSerializedRowId
DeSerializeRowId | Move (DeSerializeRowId(sSerializedRowId)) to riRowId

These functions allow you to perform any needed RowId evaluation at a low level:

Function RunOrderDtlReport RowId riHdrId Returns RowId

RowId riEnd

Boolean bFound

Move (FindByRowId(OrderHea.File_Number,riHdrId)to bFound

If bFound Begin

Set priStartRowId To (NullRowId())

Get DoRunReport To iStat

Get priEndRowId To riEnd

End

Else Begin

Move (NullRowId()) To riEnd

end

Function_Return riEnd

End_Function

The RowId Data Dictionary Interface

Data Dictionaries also provide a complete interface for working with RowIds.

Send FindByRowId | Send FindByRowId of hoDDO iFile riRowId
Send ReadByRowId | Send ReadByRowId of hoDDO iFile riRowId
Get CurrentRowId | Get CurrentRowId of hoDDO to riRowId
Get HasRecord | Get HasRecord of hoDDO to riRowId

These methods, along with the global RowId functions can be used to handle any type of RowId programming using Data Dictionaries.

Function RunOrderDtlReport RowId riHdrId Returns RowId

RowId riEnd

Boolean bFound

Integer iMain

Get Main_File of oOrderHea_DD to iMain

Send FindByRowId of oOrderHea_DD iMain riHdrId

Get HasRecord of oOrderHea_DD to bFound

If bFound Begin

Set priStartRowId To (NullRowId())

Get DoRunReport To iStat

Get CurrentRowId of oOrderDtl_DD To riEnd

End

Else Begin

Move (NullRowId()) To riEnd

end

Function_Return riEnd

End_Function

Special Notes

  • Do not confuse RowId with Primary Key. If your database supports recnum fields, your tables will still probably have a Primary Key (i.e., they will contain a field or set of fields that are uniquely indexed) which will be identified in your Data Dictionary class by setting the Key_Field_State property. That is the field that you will use in you relationships. While those fields could, in theory, be used to identify RowId they won’t be because the Database’s internal definition of Recnum provides the fastest way to re-find a record. You always want to use the fastest method for finding records by record-identity. Whatever method your database and your database driver supports that is fastest, should be used.

  • RowId was added to Visual DataFlex version 11.0. Prior to version 11.0, all tables needed to support a recnum (i.e., all tables needed to support a unique valued, numeric, single field). The introduction of RowId lifts this restriction. Recnum style programming is still supported. If a developer knows that all of their tables will contain a recnum, they can continue to program using the recnum commands and methods.

See Also

Basic Data Dictionary and Table Concepts


Transactions, Locking and Multi-user Support

Transactions and database locking support is built directly into Data Dictionaries. Database updates (saves and deletes) are, therefore, always multi-user safe. Managing transactions, locking and multi-user access is automatic requiring no programming effort from the developer.

Transaction rollback is also built into Data Dictionaries. If for any reason a save or delete transaction cannot be completed, the entire transaction is rolled back and no changes are committed to the database.

Locks will be placed on your Database during a transaction. The tables that participate in the transaction will be write-locked. This allows other users to read data from the tables but will not allow them to write data until the locks are released. Locks are released when a transaction has ended – either as a successful commit or a failed rollback. Depending on the Database the locks are applied at a record level or at a table level.

Transactions and Locking is discussed in more detail in Transactions and DDOs.

See Also

Basic Data Dictionary and Table Concepts


File Buffers and DDO-Field Buffers

When a Table in a database is opened, a global file buffer is created which contains the field values for a record. Record finds and clears move data from the table to this buffer. Saves move data from the buffer to the table. Global variables, called file.field variables are defined that provide program access to these buffers allowing programmers to access and change data about a current record.

// Move from file buffer to a variable

Move Customer.Name to sName

// Move from variable to the file buffer

Move sName to Customer.Name

Data Dictionary objects maintain local buffers, which maintain information about the Data Dictionary object’s current record. These are called DDO-Field Buffers. These local buffers allow applications to support of multiple Data Dictionary objects that use the same table.

// Move from DDO-Field buffer to a variable

Get Field_Current_Value of hoDDO Field Customer.Name to sName

// Move from variable to the DDO-Field buffer

Set Field_Changed_Value of hoDDO Field Customer.Name to sName

There will be times when you want to interact directly with the global file buffers and times when you want to interact with the DDO-Field buffers. This is discussed in detail in Understanding File Buffers and DDO Field Buffers.

See Also

Basic Data Dictionary and Table Concepts


The File_Field and Field Keywords

Data Dictionary field information is stored and accessed using a field interface. This interface requires that you pass a field identifier and sometimes a table identifier as parameters. These parameters must be passed as integer values. Special keywords, Field and File_Field, are provided that allow you to pass these parameters as names instead of numbers. The compiler will convert these to the appropriate number constants. Examples of this keyword usage are:

// use of the field keyword

Get Field_Current_Value of oCustomer_DD Field Customer.Name to sName

Set Field_Changed_Value of oCustomer_DD Field Customer.Name to sName

// use of the File_Field keyword

Get File_Field_Current_Value of oOrder_DD File_Field SalesP.Name to sName

Set File_Field_Changed_Value of oOrder_DD File_Field SalesP.Name to sName

The Field keyword is used with the Field style messages. You use these when the Data Dictionary Object receiving the message “owns” the field. In the above example, Customer.Name is defined within the oCustomer_DD DDO.

The File_Field keyword is used with the File_Field style messages. You use these when the Data Dictionary Object receiving the message may not “own” the field – the field may be owned by the DDO or by one of its parent DDOs. In the above example, SalesP.Name is defined within the oOrder_DD DDO but the field is “owned” by the parent DDO, oSales_DD. File_Field is used in such a case and the DDO will redirect the message to the appropriate DDO.

The File_Field and Field keywords are discussed in greater detail in Understanding the File_Field and Field Keywords.

See Also

Basic Data Dictionary and Table Concepts


The File Commands

The DataFlex Language supports a number of commands that allow you to directly access tables outside of Data Dictionaries. Those commands are:

  • Find

  • Clear

  • Save and SaveRecord

  • Delete

  • Open and Close

  • Relate

  • Attach

When using Data Dictionaries you will normally not need to use these commands. In some of the Data Dictionary events you may use these commands to handle custom low level operations. For example, you may wish to perform some kind of customized find in a relate process in Relate_Main_File. In that case you may use these commands.

Additionally, you can use these commands and bypass the use of a Data Dictionary. You are most likely to do this when finding records. Using direct command to save or delete records is not recommended because you are bypassing all of the validation rules that the DDOs enforce.

See Also

Basic Data Dictionary and Table Concepts


The Database API

The DataFlex language has a set of commands, which allow you to query any piece of information about your tables. These are referred to as Database API attributes. The command Get_Attribute is used to query the value of any API attribute. You can use this to query information about:

  • A table (e.g. name, description, number of fields, number of indexes, number of records),

  • A table’s index (e.g. number of index segments, value of each segment)

  • A table’s field (e.g. data type, length, decimal points, field-name)

Rather than creating an additional layer of DDO interface to access these values, it is expected that you access this information within a DDO or a component directly, using the Get_Attribute command. Most of these attributes are low level – you should rarely need to access them. If you cannot find a DDO message that returns the information you need, check the API attributes. Within an application, the most commonly required API attributes will be field length and field data type.

The Set_Attribute command can also be used to change database attributes. You would rarely need to do this within an Application.

See Also

Basic Data Dictionary and Table Concepts


Defining Data Dictionary Classes

Before you can use Data Dictionaries in your application you must create a class for each Table in your application. Database Builder provides you with a tool that does this for you. Although Database Builder is a visual design tool, the end of result of this tool is source code. Data Dictionaries are simply source code based classes that are compiled into your application.

The purpose of this section is to describe the features of a Data Dictionary class and to expose the Data Dictionary interface (i.e., expose the source code). We expect that you will always use Database Builder to maintain your Dictionaries. Some of the code within Data Dictionary classes will entirely created and maintained by Database Builder’s point and click interface. Other parts of the class, in particular the events, will require that you create custom code.

Some of the properties and events described in this class section can also be defined at the Data Dictionary object level. When working at the object level you will need to know how to use and code the interface.

A Data Dictionary class can be divided into two main parts:

  • The Data Dictionary Constructor: The constructor method is used to set all Data Dictionary attributes. This includes table attributes, Field attributes, Foreign Field attributes and Parent/Child table relationships. All of these attributes are maintained by setting properties. These are all maintained inside of a procedure named Define Fields.

  • The Data Dictionary Events: A number of events are called during the Data Dictionary find, save, delete, and clear operations. These events may be created to handle the custom requirements for a table. These requirements include setting defaults, performing validations, maintaining balances between tables, and controlling custom relationships between tables. In addition, the developer may create custom events and assign these to fields. These events are triggered by specific operations such as field validation and navigations. All of these events are created as functions and procedures, which the developer must create.


The Data Dictionary Constructor (Define_Fields)

A special constructor method, Define_Fields, is used to define all attributes of a Data Dictionary. This constructor method defines the following types of information:

  • Table Attributes: These define the attributes that apply to the entire table. This connects your Data Dictionary to a specific Table and defines the basic capabilities of the Data Dictionary.

  • Field Attributes: These define attributes for each field (column) in your table. This provides field validation rules, finding rules, and much information that is used by the data entry objects.

  • Foreign Field Attributes: These define attributes for your fields when the Data Dictionary object is used in a parent (foreign) role in the DDO structure.

  • Parent / Child Table Relationships: This defines the relationship rules between tables. This is used to help build DDO structures and to verify that these structures are capable of properly supporting database updates.

All of these settings are created and maintained using Database Builder. Listed below is a sample Define_Fields that was created using Database Builder:

Procedure Define_Fields

Forward Send Define_Fields

//DDB-Generated-Code-Location

//DDB-DefineFieldStart

Set Main_File To Orderhea.File_Number

Set Foreign_Field_Options DD_KEYFIELD To DD_FINDREQ

Set Foreign_Field_Options DD_INDEXFIELD To DD_NOPUT

Set Foreign_Field_Options DD_DEFAULT To DD_DISPLAYONLY

// Child (Client) file structure................

Send Add_Client_File Orderdtl.File_Number

// Parent (Server) file structure...............

Send Add_Server_File Customer.File_Number

Send Add_Server_File Salesp.File_Number

// External (System) file structure.............

Send Add_System_File Ordsys.File_Number DD_LOCK_ON_NEW_SAVE_DELETE

Define_Auto_Increment Ordsys.Order_Number To Orderhea.Order_Number

// Field-based properties.......................

// Orderhea.Order_Number

Set Field_Options Field Orderhea.Order_Number To DD_AUTOFIND

Set Field_Prompt_Object Field Orderhea.Order_Number To (ORDERHEA_SL(Self))

Set Key_Field_State Field Orderhea.Order_Number To TRUE

Set Status_Help Field Orderhea.Order_Number To "Order Number - New orders are assigned numbers automatically"

// Orderhea.Customer_Number

// Orderhea.Order_Date

Set Field_Class_Name Field Orderhea.Order_Date To "dbSpinForm"

Set Field_Entry_msg Field Orderhea.Order_Date To Entry_Order_Date

Set Field_Mask Field Orderhea.Order_Date To "mm-dd-yyyy"

Set Field_Mask_Type Field Orderhea.Order_Date To MASK_DATE_WINDOW

Set Field_Prompt_Object Field Orderhea.Order_Date To (ORDERHEA_SL(Self))

Set Status_Help Field Orderhea.Order_Date To "Date on which the order was placed"

// Orderhea.Terms

//DDB/ Comment_Short Field Orderhea.Terms To "Where does this go?"

Set Field_Class_Name Field Orderhea.Terms To "dbComboForm"

Set Field_Label_Long Field Orderhea.Terms To "Terms"

Set Field_Label_Short Field Orderhea.Terms To "Trm"

Set Field_Value_Table Field Orderhea.Terms To (Terms_table(Self))

Set Status_Help Field Orderhea.Terms To "Payment terms"

// Orderhea.Ship_Via

Set Field_Class_Name Field Orderhea.Ship_Via To "dbComboForm"

Set Field_Value_Table Field Orderhea.Ship_Via To (Ship_Table(Self))

Set Status_Help Field Orderhea.Ship_Via To "Shipping method"

// Orderhea.Ordered_By

//DDB/ Comment_Short Field Orderhea.Ordered_By To "This is just a suggested list. No parent files support this"

Set Status_Help Field Orderhea.Ordered_By To "Order placed by"

// Orderhea.Salesperson_Id

Set Field_Label_Long Field Orderhea.Salesperson_Id To "Sales Person Id"

Set Field_Label_Short Field Orderhea.Salesperson_Id To "Sales ID"

Set Status_Help Field Orderhea.Salesperson_Id To "Sales Person who initiated the order"

// Orderhea.Order_Total

//DDB/ Comment_Short Field Orderhea.Order_Total To "Maintained by the Orderdtl DD"

Set Field_Mask_Type Field Orderhea.Order_Total To MASK_CURRENCY_WINDOW

Set Field_Options Field Orderhea.Order_Total To DD_DISPLAYONLY

// Orderhea.Last_Detail_Num

//DDB/ Comment_Short Field Orderhea.Last_Detail_Num To "This is the "

//DDB-DefineFieldEnd

End_Procedure // Define_Fields

The Define_Fields constructor can only be used in classes. You cannot create or augment Define_Fields within a Data Dictionary object.

See Also

Defining Data Dictionary Classes


Defining Data Dictionary Table Attributes

The Data Dictionary Table attributes define the attributes that apply to the entire table. These attributes are used to:

  • Set your Main_File

  • Define your Key Fields

  • Define Save and Deletion Protection


Main_File

Set Main_File to Customer.File_Number

This identifies the Data Dictionary’s table. It must always be set and once set cannot be changed. Setting this property is the minimum requirement of a Data Dictionary. The most basic Data Dictionary class could be created as follows:

Class cTheMostSimpleCustomer_DD is a DataDictionary

Procedure Define_Fields

Forward Send Define_Fields

set Main_File to MyTable.File_Number

End_Procedure

End_Class

See Also

Defining Data Dictionary Table Attributes


Key Fields

Set Protect_Key_State to true

Set Key_Field_State Field Customer.Id to true

Key fields are fields that have been designated to uniquely identify records in a table (e.g., customer_number in a customer table). The property used to designate key fields is Key_Field_State.

After you have designated key fields, you may set options for them all with Foreign_Field_Options.

Key_Field_State

Set Key_Field_State Field Customer.Id to true

Key_Field_State marks a field as the primary key field for the table.

One use of setting this option can be to prevent changes to the data stored in the field. This is known as key-field protection. Key-field protection allows data to be entered into the key field only when the record is first created. Once the record is created, the key-field data cannot be changed.

You would normally set this option on primary key fields such as the Order Number field in an Order Header table or the Customer ID field in the Customer table. This offers orphan prevention by preventing the ID fields used in parent-child relationships from being changed.

If your primary Key consists of multiple field segments, all fields in the key should be marked as Key Fields.

Key fields are only protected in a table if the Protect_Key_State property is also set in the Data Dictionary.

Protect_Key_State

Set Protect_Key_State to true

Protect_Key_State determines if the fields that make up a primary key should be “protected” after they have been saved the first time in a new record.

When set to true, a field identified as being part of a key cannot be edited after it has been saved. This ensures that child records that relate to a parent through a primary key will not become accidentally orphaned.

The fields that make up a primary key are identified by using the Key_Field_State field property.

See Also

Defining Data Dictionary Table Attributes


Save and Deletion Protection

Read_Only_State

Set Read_Only_State to True

Read_Only_State is used to specify that the Data Dictionary cannot be directly used to save or delete records. This means that the messages Request_Save and Request_Delete sent to this DDO will generate an error and not perform an update.

Note that records in a read-only Data Dictionary can be indirectly changed by a save or delete sent to another Data Dictionary within the object structure. For example, if the parent Customer DDO is set to read-only and the main Order Header DDO is not set to read-only, then saves and deletes in Order Header DDO will be allowed to change this Customer data. This is required to maintain database integrity. If you want an entire DDO structure to be read-only you should set all DDOs in the structure to read-only.

Read_Only_State is more likely to be set in a Data Dictionary object and not the class.

No_Delete_State

Set No_Delete_State to True

No_Delete_State disables direct deletes of records. This means that the message Request_Delete sent to this DDO will generate an error and not perform any action.

This message does not affect deletions of records that are part of a cascade delete.

Cascade_Delete_State

Set Cascade_Delete_State to True

Cascade_Delete_State specifies how a delete should be affected by the existence of child-file records that relate to the record being deleted.

If Cascade_Delete_State is true, then the child records and all descendant records will be deleted.

If the property is false, the delete will only be allowed if no child records exist.

Both methods ensure that child-file records will never become orphan records.

Validate_Foreign_File_State

Set Validate_Foreign_File_State to False

Before a save occurs, you will want to validation all fields by sending the Request_Validate message to the DDO performing the save. Normally all fields in all tables that participate in a save will be validated. This includes fields that have no visual representation in the current view and this includes all fields in all ancestor tables. In almost all cases, this level of data protection is desired, and is a powerful feature of the data dictionary. In rare cases, you may wish to select lower levels of validation protection.

The property Validate_Foreign_File_State determines if field validation should be applied to foreign (parent) tables. Normally you want this level of validation, so its default value is true. Setting its value to false will skip field validation when the table is used as a parent table in your DDO structure. It will also suppress validation in all additional ancestor tables.

Setting this property away from their defaults essentially defeats full field validation and potentially compromises data integrity. They should only be used under carefully controlled circumstances.

Do not use this property as short-cut solutions to bad database design. Special conditional validation rules can be programmed directly into your validation routines. For example, you could build a validation routine that only applies a field validation to new records (check the record’s status with the HasRecord property) or you could create validation routines that selectively skip certain fields when they are foreign (the Operation_Origin global integer tells you which DDO started the save). Rather than breaking a rule, attempt to define this rule change as part of your rule set. In the end, you will have a better, more-robust application.

See Also

Defining Data Dictionary Table Attributes


Defining Data Dictionary Field Attributes

Field Attributes allows you to define extensive information about each field in your table. As you develop your application this information will be used by the Studio and its wizards to automate much of the development process. When your program runs, this information will be used to assist in navigation, assist in data entry, provide context help, provide validations and more.

Setting Field attributes consists of defining attributes for:

  • Field Options

  • Field Validations

  • Field Appearances

  • Field Entry and Exit Methods

  • Auto-Increment Fields

  • Field Lookup Lists


Field_Options

The Field_Options property is used to set a number of options for any field. These options determine a variety of attributes including finding behaviors (e.g., Auto-Find, Find-Required), data entry behaviors (e.g., Capslock, No-Enter) and update behaviors (e.g., No-Put, Force-Put). These options are defined with various “DD_” constants and are listed below.

The options below shown with an asterisk (*) are finding options, and may be used only on indexed fields. The other options may be used with any field.

Auto-Find | DD_AutoFind *
Auto-Find GE | DD_AutoFind_GE *
Uppercase | DD_CapsLock
Display-Only | DD_DisplayOnly
Find-Required | DD_FindReq *
Force-Put | DD_ForcePut
No-Enter | DD_NoEnter
No-Put | DD_NoPut
Retain | DD_Retain
Retain-All | DD_RetainAll
Required | DD_Required
Skip-Found | DD_SkipFound
Zero-Suppress | DD_Zero_Suppress

For example, you could define that a field should be No-Put, Auto-Find and Capslock by setting the following properties:

Set Field_Options field Customer.Id to DD_NoPut

Set Field_Options field Customer.Id to DD_AutoFind

Set Field_Options field Customer.Id to DD_CapsLock

Because multiple options must often be applied to the same field, you are allowed to combine multiple options in a single line as follows:

Set Field_Options field Customer.Id to DD_NoPut DD_AutoFind DD_CapsLock

Field_Options can be set and changed at the DD object level. When used at the object level you will not use the Field_Options message. Instead you will use Field_Option, Field_Option_Clear and Field_Option_Toggle. See Changing Field Properties within a DD Object for more information.

Set Field_Option field Customer.State to DD_Retain

Set Field_Option_Clear field Customer.State to DD_Retain

Set Field_Option_Toggle field Customer.State to DD_Retain

Auto-Find (DD_AutoFind)

Set Field_Options field Customer.Id to DD_AutoFind

DD_AutoFind executes an automatic find eq (equals) by the Main Index of the field when the user navigates out of a data-entry form for the field. This option will not work on a field that does not have a main index.

If a record is found whose value in the main index of the field exactly matches the values in the data-entry form and any other items whose fields participate in the index, it is displayed to the screen. If no such record is found, the cursor exits the form normally and no record is moved into the buffer. No error will be declared.

You would usually set this option on every key field in your database.

Where you wish to find by a non-unique index, use Auto-Find GE.

Where you are auto finding by a multi-segment index, place the DD_AutoFind option on the last field that participates in the index.

If used together with Find-Required, Auto-find will require users to enter a valid existing key before continuing.

Auto-Find GE (DD_AutoFind_GE)

Set Field_Options field Customer.Id to DD_AutoFind_GE

DD_AutoFind_GE performs a find ge (greater than or equal to) by the main index of the field when the user navigates out of a data-entry form for the field. This option will not work on a field that does not have a main index. If a record is found whose value in the main index of the field exactly matches the values in the field’s data-entry form and any other forms whose fields participate in the index, it is displayed to the screen. If no such record is found, the record whose index value next follows that in the form(s) is displayed.

Auto-Find GE is useful for finding records on partial entries to items or, in multi-segment indexes, by keys for only the first segment or segments (rather than all of them). All non-unique indexes, for example, are multi-segment (being composed of at least one field plus a record ID field).

You may use Auto-Find GE with any indexed field regardless of whether the field’s main index is unique or non-unique.

If you are using Auto-Find GE by a multi-segment index, place the DD_AutoFind_GE option on the last field in the index for which you wish to use an entered key value. If any field that comes before the one with the DD_AutoFind_GE option on it in the index lacks an entered key value in its form, the value of that field will be considered empty for purposes of the find. The found record will be the one whose value for the first such field is the lowest in the index.

If you want to execute an automatic find eq (equals) command for an exact match with the entered data, use the Auto-Find option instead (above).

Auto-Find GE will always find a record, unless the table has no records in it.

Uppercase (DD_CapsLock)

Set Field_Options field Customer.Id to DD_CapsLock

DD_CapsLock automatically converts any lower-case characters entered into a data-entry form associated with the field, to upper case (capital letters). It has no effect on non-alphabetic characters. This assures that all input to the field through data-entry forms will be saved with letters in upper case.

Force-Put (DD_ForcePut)

Set Field_Options field Customer.Id to DD_ForcePut

DD_ForcePut forces the contents of data-entry forms connected to the field to be put to the table’s record buffer before a save even if no changes were made to the form.

Normally the contents of a data-entry form are put to the buffer only if they have been changed from what was originally displayed from the disk

No-Enter (DD_NoEnter)

Set Field_Options field Customer.Id to DD_NoEnter

DD_NoEnter prevents data entry through any form associated with the field.

Special Note:

The Skip-Found option (DD_SkipFound) has a similar effect, except that it allows the cursor to go to the item only if no record is found. No-Enter skips the form regardless of whether a record is found.

The DD_DisplayOnly option is a combination of DD_NoEnter and DD_NoPut.

Although data cannot be entered from the keyboard to a form controlled by No-Enter, data can still be moved to the form under program control. Such data changes will be moved to the record buffer and saved, unlike with the Display-Only or No-Put field options.

No-Put (DD_NoPut)

Set Field_Options field Customer.Id to DD_NoPut

DD_NoPut stops data being moved from the data-entry form back to the record buffer for the field, even if new data is entered from the keyboard. No-Put is useful for allowing data to be entered into a form for finding, where you do not wish to allow the field to be changed.

Use the No-Put field option when you want to prevent users from changing data.

The DD_DisplayOnly option is a combination of DD_NoEnter and DD_NoPut.

Display-Only (DD_DisplayOnly)

Set Field_Options field Customer.Id to DD_DisplayOnly

DD_DisplayOnly causes the cursor to skip the data-entry form associated with this field and prevents any data in the form from being changed. The form will display the field data as disabled.

DD_DisplayOnly is the equivalent of a combination of the DD_NoEnter and DD_NoPut options

// This is the same as DD_DisplayOnly

Set Field_Options field Customer.Id to DD_NoEnter DD_NoPut

Retain (DD_Retain)

Set Field_Options field Customer.Id to DD_Retain

DD_Retain prevents data-entry forms associated with the field from clearing when the user initiates an operation that would normally clear all the forms in a view.

If the user initiates a clear-all operation, then even retained data-entry forms will be cleared.

The choice to retain or not retain a value is not something that can usually be applied to all objects in all views using a particular DD class. Instead retains should be applied based on the data entry requirements of a particular view. Therefore, it is really expected that Retain will be applied at the DD object level and not as part of the class definition.

Retain-All (DD_RetainAll)

Set Field_Options field Customer.Id to DD_RetainAll

DD_RetainAll prevents data-entry forms associated with the field from ever being cleared.

Even though users cannot clear the data-entry form by pressing the Clear key, they still may erase it manually (character-by-character).

The choice to retain or not retain a value is not something that can usually be applied to all objects in all views using a particular DD class. Instead retains should be applied based on the data entry requirements of a particular view. Therefore, it is really expected that Retain will be applied at the DD object level and not as part of the class definition.

Skip-Found (DD_SkipFound)

Set Field_Options field Customer.Id to DD_SkipFound

DD_SkipFound prevents data entry to the field’s data-entry form if the buffer for the table has an active record in it.

DD_NoEnter is a similar field option. However, it skips the form in all cases, regardless of whether an active record is in the buffer.

Zero-Suppress (DD_Zero_Suppress)

Set Field_Options field Customer.Id to DD_Zero_Suppress

DD_Zero_Suppress blanks the display of the field’s data-entry form if it is a numeric field with value of zero (0).

DD_Zero_Suppress will appear not to work where a small absolute value is put to a form. For example, if a form specifies integer-only display and a field value less than 1 is put to it (say, 0.5), a zero will display in the form. Only a value that is truly zero will be displayed as blanks.

Required (DD_Required)

Set Field_Options field Customer.Id to DD_Required

DD_Required ensures that data is entered into a field.

Any character or characters entered in the data-entry form will allow the cursor to move on to the next form.

This required validation is also applied during a save.

Find-Required (DD_FindReq)

Set Field_Options field Customer.Id to DD_FindReq

DD_FindReq prevents the cursor from going to the next data-entry form until a successful find on the form connected to this field places an active record in the view. An unsuccessful find causes Error 90 (Please enter a valid record ID).

Find required has no effect if an active record for the field’s table is already in the view.

You would normally apply Find Required only as a foreign-field option in a parent table to ensure that a related parent record is found before a save occurs in the child.

An Auto-Find option on the field with a Find Required will attempt the required find automatically when a user attempts to move the cursor out of the form. If the automatic find is successful, the cursor will proceed. If it is not, Error 90, Please enter a valid record ID, is declared.

A Find Required with an Auto-Find GE is unnecessary. Auto Find GE always finds a record in a non-empty database file.

This find required validation is also applied during a save.

Clearing Field Options

Additional messages are supported to allow item options to be cleared or toggled:

Field_Option_Clear (and File_Field_Option_Clear) clears the field options passed by this message.

Set Field_Option_Clear Field Customer.Name to DD_Retain

Field_Option_Toggle (and File_Field_Option_Toggle) toggles the field options passed by this message.

Set Field_Option_Toggle Field Customer.Name to DD_Retain

These messages are rarely used in Data Dictionary classes. When they are used, they will be used in Data Dictionary objects to dynamically control a data entry view. See Changing Field Properties within a DD Object for more information.

See Also

Defining Data Dictionary Field Attributes


Field Validations

Field Validations are used to verify that a field value matches a valid value. One of four types of field validations may be defined:

  • Checkbox - value entered must be one of two values - most often used with checkbox entry items.

  • Range - value entered must be within the numeric or date range

  • Check - value entered must match item within a “check string” – a string of valid values separated by the “|” character.

  • Validation Table – value entered must match a value within a validation table object. A special validation table called a Code Validation Table provides a way to easily assign a set of valid codes and descriptions to any field.

In addition, for any field you may define a custom field validation method. This method, a function, is assigned to a field by setting the field’s field validation message. During validation this function will be called. If an error is detected, the function should generate an error and return a non-zero value. This function allows you to create any kind of validation rule you need.

Field validation occurs during forward navigation and during saves. Using field validations ensures that the data being saved to your database is valid.

Checkbox

Set Field_Checkbox_Values field Customer.status to "A" "I"

The Field_Checkbox_Values message sets the field up for use in a checkbox control. A checkbox field can only store one of two values; a true value and a false value.

If the field is represented by a checkbox control (dbCheckbox), then the control will be checked when the field value equates to true, and is unchecked when the field value equates to false.

If the field is represented by a form control (dbForm) then data entry will be restricted to either the true value or the false value.

When a validation error occurs, the error number and text specified for the field in the Field_Error property are used to report an error. If no text was defined, a standard check-error text will be reported.

Range

Set Field_Value_Range field Customer.discount to 0 60

The Field_Value_Range method allows you to define a minimum and maximum range for the field. It is expected that the field and its range will be Numeric or Date. The From value must be lower than the To value.

The validation is performed after the user has entered data into the field’s data-entry form. If the validation fails, then the user is prohibited from leaving the form. (The user may still exit the form by clicking elsewhere on the screen with the mouse).

When a validation error occurs, the error number and text specified for the field in the Field_Error property are used to report an error. If no text was defined, a standard check-error text will be reported.

The validation is also performed when a record is saved. If the validation fails, the save is aborted.

Check

Set Field_Value_Check field Customer.region to "N|S|E|W"

The Field_Value_Check message is used when the list of valid field values is limited to a static, small set of simple values. These samples are identified in a "check" string. This consists of a list of valid values separated by the | symbol. For example, the choices A, B and C would be represented by the check string A|B|C. The choices CA, FL, and TX would be represented by the check string CA|FL|TX.

The validation is performed after the user has entered data into the field’s data-entry form. If the validation fails, then the user is prohibited from leaving the form. (The user may still exit the form by clicking elsewhere on the screen with the mouse). The validation is also performed when a record is saved. If the validation fails, the save is aborted.

In addition to being used for validation, these values may also be used with combo-box entry items. If a dbComboForm is assigned to a field using check values, each value in the check string will be loaded into the combo list.

Set Field_Value_Check field Customer.state to "CA|FL|NY|AZ"

The Customer.state example above is probably not a good usage of this validation method. Adding new states to this list requires program changes, and the string may quickly become too large to be practical. In such a case, you will want to use a validation table to provide the values.

When a validation error occurs, the error number and text specified for the field in the Field_Error property are used to report an error. If no text was defined, a standard check-error text will be reported.

The Check validation is limited. A more flexible and reusable way of specifying a list of valid entries is to use a Validation Table.

Validation Tables

Set Field_Value_Table Field Customer.Status to oStatusTable

If a field needs to be validated against one of two values, a limited set of values, or a range of values, you can use the checkbox, range or check validations. In many cases you will find that these types of validations are too limited for your needs. In such a case, you may use field validation tables.

Using a field-validation table has the following advantages:

  • You can specify a larger number of valid values than can easily fit within a check string

  • You can specify and display a description for each value

  • You can dynamically maintain your list of values (i.e., the values do not have to be coded directly in your program)

  • You can make the list choices optional (i.e., you can display a list of suggested values during data entry but users are not required to make a selection from the list)

When a validation error occurs, the error number and text specified for the field in the Field_Error property are used to report an error. If no text was defined, a standard check-error text will be reported.

The use of validation tables provides tremendous flexibility. They will be discussed in greater detail in Using Validation Tables. For now, we will introduce the primary validation-table classes:

ValidationTable

This class allows you to maintain a list of valid values.

Object oStatusTable is a ValidationTable

Procedure Fill_List

Send add_table_value "O"

Send add_table_value "C"

Send add_table_value "D"

End_Procedure

End_Object

DescriptionValidationTable

This class allows you to maintain a list of valid values and their associated descriptions.

Object oStatusTable is a DescriptionValidationTable

Set List_Title to "Customer Status"

Procedure Fill_List

Send add_table_value "O" "Opened"

Send add_table_value "C" "Closed"

Send add_table_value "D" "Flagged for Deletion"

End_Procedure

End_Object

FileValidationTable

This class allows you to maintain a list of values and descriptions that can be easily loaded from a specified Visual DataFlex table.

Object Status_Table is a FileValidationTable

Set Main_File to CustStat.File_Number

End_Object

CodeValidationTable

This class allows you to load your data and description values from the Visual DataFlex Code List.

Object oStatusTable is a CodeValidationTable

Set Type_Value to "Status"

End_Object

Any of the above validation tables can be linked to a field with the Field_Value_Table message in a data dictionary.

Set Field_Value_Table Field Customer.Status to oStatusTable

Field Validation Methods

Set Field_Validate_msg field employee.pay_type to get_valid_pay_type

The Field_Validate_msg property lets you assign a general-purpose message to be sent when a field should be validated. This message will be the message-Id of a function and will get called whenever your program requires a validation. This validation method is used when you need to perform complex validations that can only be expressed with code. Since it is a very open-ended process, it can do just about anything. If the value is valid, the function should return a zero. If the value is invalid, the function should generate an error and return a non-zero value.

This validation method can be used in conjunction with the other validation techniques (checkbox, range, check, validation table) or as a replacement to them.

You must create the validation function(s). This function will get passed the field number and the current field value. Your function may use these values as it sees fit. For this purpose, the value of any other field in the data dictionary may be obtained by getting the Field_Current_Value property. It is important to note that the validation routine should never need to access a value in a DEO. The information required should be found in the DDO or in one of the DDOs connected to it.

For example, if you have the following member function…

Function ValidateCredit Integer iField Number nValue ;

Returns Boolean

Boolean bErr

String sCustRating

// get the customer rating for this customer from DD buffer

Get Field_Current_Value field Customer.Rating to sCustRating

// if Customer Rating is A1 their limit is 5000

If (sCustRating="A1" and nValue > 5000) Begin

Error DFERR_OPERATOR "Credit limit may not exceed $5000"

Move True To bErr

End

Function_Return bErr

End_Function

To use this function on the Credit_Limit field of a file, you must set the field’s validate method (Field_Validate_msg) to the handle name of the function. The handle name of a function is the name of the function prefixed with “get_” (e.g., get_ValidateCredit).Set Field_Validate_msg field Customer.Credit_Limit to get_ValidateCredit

The declaration prototype for a validation method takes the following general form.

Function function_name integer iField type CurrentValue returns integer

Where:

  • Function_name is the name of the validation function;

  • iField is the field number of the field that sent function_name. The field number can be used to retrieve any information about the field; and,

  • type and CurrentValue are the data type and current value of the field that sent function_name.

This allows you to write generalized validation-method functions that can be re-used for other fields and tables.

Field Validation Errors

Set Field_Error field Customer.Credit_Limit to 200 "Not a legal number"

The Field_Error method allows you to define a specific error number and message to be raised whenever validation fails for the field. This means that each field can have its own specific validation error message.

The error is triggered when one of the following validation tests fails: checkbox, range, check or validation table. Validation Methods should have their own error texts coded inside the member function.

See Also

Defining Data Dictionary Field Attributes


Field Appearance

Masks

Set Field_Mask Field Orderhea.Order_Date To "mm-dd-yyyy"

Set Field_Mask_Type Field Orderhea.Order_Date To MASK_DATE_WINDOW

Masks are used within Visual DataFlex windows applications to:

  • Restrict data entry to a field to a defined pattern of numbers, letters, and punctuation marks.

  • Provide displays of field values complying with pre-defined patterns and formatting. For example, it is possible to display date values as abbreviations such as days of the week or months of the year. Translating from numbers to names is sensitive to the Windows locale settings at using sites.

Masking is not validation and should not be used as a validation technique.

Masks are defined by setting two field properties: Field_Mask_Type and Field_Mask.

Masking is discussed in more detail in Masks in Windows Applications.

Labels

Set Field_Label_Long Field Orderhea.Terms To "Terms of Payment"

Set Field_Label_Short Field Orderhea.Terms To "Terms"

Long and Short labels may be assigned to every field. Long and short field labels are defined using the Field_Label_Long and Field_Label_Short properties.

When getting the value of a label you should use the Field_Label function.

Get Field_Label Field DD_LABEL_SHORT To sHeaderName

Get Field_Label Field DD_LABEL_LONG To sLabel

The Field_Label (and File_Field_Label) function provides additional flexibility in determining a label’s value.

  • If a long label is requested and the long label name is not assigned, a label will be returned using the smart-cased name of the field.

  • If a short label is requested and the short label name is not assigned, the long label name will be returned. If the long label is not assigned, it will return a smart-cased field name.

The same logic is applied when the Studio or the Wizards are used to design views and reports. The Studio will apply the long label whenever a form-style control and reports (e.g., dbForm, dbComboForm) are created. The studio will apply a short label when column style controls and reports are created (e.g., dbGrid).

Status Help

Set Status_Help field Cust.Status to "Customer is (A)ctive or (I)nactive"

Status_Help defines a status-help text line for the field. Entry objects will use this to display status-bar help. When the user moves the cursor to a data-entry object for the field, the Status Help is displayed in the status bar.

Enter text that will aid the user with data entry to this field.

Windows Controls

Set Field_Class_Name field Cust.Address to "cDbAddressForm"

Field_Class_Name assigns the class name of a specific control to each field. By default, the Studio will use this information to determine the class of control that is created whenever the field is dragged onto a component. This setting does not stop you from explicitly choosing a different control class for the field in the Studio, or from changing the class of control on an individual basis; it is only the default.

If you leave this blank, the Studio will determine the best control to use for the field. The Studio does a good job of assigning the right control, so this property will usually be left empty.

You can enter any class name in this form. If you enter the name of a non-standard class, then the class must be correctly registered with the Studio before it can be used.

See Also

Defining Data Dictionary Field Attributes


Entry and Exit Methods

The Field_Entry_msg and Field_Exit_msg properties are similar to the Field_Validate_msg and are sent whenever the cursor enters or exits an item connected to a field. You would create the procedures and then assign the procedure names to the field properties. When called, the procedures are passed the field number and field value. While returning a non-zero would stop the navigation event, this would be unusual usage. These procedures are usually used to handle pre-entry or post-exit processing.

Entry Method

Set Field_Entry_msg Field Customer.State To EntryCustomerState

Field_Entry_msg allows you to specify the name of a method that is executed whenever the cursor moves into a data-entry form connected to the field.

The Entry Method, a procedure, can be programmed to perform any action. For example, you might use it to display special information or default values each time the cursor goes to the field for data entry.

In Visual DataFlex, procedures can return an Integer value. In the case of an Entry Method, if a non-zero value is returned, then the cursor movement into the field’s data-entry form is aborted. Using the entry method event to control navigation is strongly discouraged.

The Entry Method should be a member procedure of the Table’s Data Dictionary class.

For example if you have the following member procedure

Procedure EntryOrderDate Integer iField Date dDate

// Add a default date if the field is blank

Boolean bChanged

Get Field_Changed_State iField to bChanged

If (not(bChanged) AND dDate =0) Begin

SysDate dDate

Set Field_Default_Value iField to dDate

End

End_Procedure

To use this procedure on a date field of a file, you would set the field’s Entry Method to EntryOrderDate. You are actually assigning the method handle msg_EntryOrderDate but the “msg” prefix, if omitted, will be supplied automatically.

Set Field_Entry_msg Field Orderhea.Order_Date To EntryOrderDate

The declaration prototype for an Entry Method takes the following general form.

procedure procedureName integer iField type currentValue

Where:

  • procedureName is the name of the procedure;

  • iField is the field number of the field that sent procedureName. The field number can be used to retrieve any information about the field; and,

  • type and currentValue are the type and current value of the field that sent procedureName.

The parameters that are passed to the entry method allow you to write generalized entry-method procedures that can be re-used in other fields and tables.

Exit Method

Set Field_Exit_msg Field Customer.Zip To ExitAdjustZip

Field_Exit_msg allows you to specify the name of a method that is executed whenever the cursor moves out of a data-entry form connected to the field.

The Exit Method, a procedure, can be programmed to perform any action. For example, you might use it to adjust the values of some calculated data that is dependent on the field value being entered.

In Visual DataFlex, procedures can return an Integer value. In the case of an Exit Method, if a non-zero value is returned, then the cursor movement out of the field’s data-entry form is aborted. Using the exit method event to control navigation is strongly discouraged.

The Exit Method should be a member procedure of the Table’s Data Dictionary class.

For example if you have the following member procedure

Procedure AdjustDisplayTotal Integer iField Integer iValue

// This updates the extended price field, which will update any

// display balances. This is only done for display purposes. The

// actual amount is updated to the field during the save.

Integer iQty

Number nAmnt

Get Field_Current_Value Field Orderdtl.Qty_Ordered to iQty

Get Field_Current_Value Field Orderdtl.Price to nAmnt

Set Field_Current_Value Field Orderdtl.Extended_Price to (nAmnt *iQty)

// note we set value, but not changed state!

End_Procedure

To use this procedure on the appropriate field of a table, you would set the field’s Entry Method to AdjustDisplayTotal. You are actually assigning the method handle msg_AdjustDisplayTotal but the “msg” prefix, if omitted, will be supplied automatically.

Set Field_Exit_msg Field Orderdtl.Qty_Ordered To Adjust_Display_Total

The declaration prototype for an exit method takes the following general form.

procedure procedureName integer iField type currentValue

Where:

  • procedureName is the name of the procedure;

  • iField is the number of the field that sent procedureName. The field number can be used to retrieve any information about the field; and,

  • type and currentValue are the type and current value of the field that sent procedureName.

The parameters that are passed to the exit method allow you to write generalized procedures that can be re-used in other fields and tables.

See Also

Defining Data Dictionary Field Attributes


Auto-Increment Field

Each record in a Table should contain a field or set of fields that provides the record a unique ID. This will usually be your Primary key. In some cases, the value of the primary key must be manually provided by the operator (e.g. entering a unique string ID as part of data input). In some cases, the back-end database will provide the primary key automatically when the record is saved. In other cases, the Data Dictionary must to provide the unique ID when the record is first saved.

The auto-increment feature of the Data Dictionary provides this facility by automatically assigning unique sequential incrementing numbers for each new record.

In order for the Data Dictionary to know what next increment value will be for a new record, it must store the last assigned number in some other external table. To create an auto-increment field you will need to define the table and field of the external table as well as the ID field in your Data Dictionary. A special command, Define_Auto_Increment, is provided to do this:

Define_Auto_Increment ordsys.last_cust_num to Customer.cust_number

In this example, the field last _cust_num in the system table ordsys is designated to increment and provide a value to the field cust_number in the Table Customer.

Auto incrementing will only work correctly if the external table is a (one record) system table or is a related parent table.

You will use a system table if the unique ID is single segment as is shown in the above example with cust_number. If you are using a field from a system file, you need to register this table as an external table so that it will be correctly locked during save operations. For example:

Define_Auto_Increment ordsys.last_cust_num to Customer.cust_number

Send Add_System_File ordsys.last_cust_num DD_LOCK_ON_NEW_SAVE_DELETE

You may use a related parent table if your unique ID is multi-segment. For example, an order-detail ID may consist of an Order Number (OrderDtl.Order_number) and a system assigned detail number (OrderDtl.Detail_Number). If the last detail number was stored in each Order within the Order header table (OrderHea.Last_Detail_Num) the auto increment field would be defined as:

Define_Auto_Increment OrderHea.Last_Detail_Num to OrderDtl.Detail_Number

You may only assign one auto increment field per DDO. If you attempt to assign two, then the first field’s auto-increment setting will be erased. If you need to assign additional auto-increment fields within your Data Dictionary you may easily do this by adding custom code to the Data Dictionary Creating event.

See Also

Defining Data Dictionary Field Attributes


Lookup Lists

Set Field_Prompt_Object field Customer.name to Cust_lkup

Lookup lists are created to provide an easy way to lookup records in your application. Typically each table will have at least one lookup list associated with it. Columns in this lookup list will often consist of the most important fields in your table. These fields are often indexed and lookup lists are designed to let you easily search for records in any of these index orders.

You assign fields in a Data Dictionary to a lookup just be setting the Field_Prompt_Object property. Once assigned your windows applications will automatically attach those lookup lists to data entry objects that use that field.

You will want to create your lookup list objects before you assign them to a Field_Prompt_Object. You must know the name of the lookup list object before you assign it. Wizards are provided that allow you to create your lookup lists and assign them to the appropriate Data Dictionary fields in a single step.

If you are using one of the validation types (checkbox, range, check or validation table) the Data Dictionary is smart enough to be able to provide a default prompt list. It does this if there is no prompt list explicitly assigned. So if you assign a Field_Prompt_Object for a field, it will get used in place of the prompt list provided for extended validations.

See Also

Defining Data Dictionary Field Attributes


Defining Data Dictionary Foreign Fields Attributes

Within your data entry object the Entry_Item command binds a table and table-field value to a form or to a column within a grid. This table will either be the main file of the DEO’s Data Dictionary, or it will be a parent table. When used as a parent table, the Entry_Item is considered to be a connected to a "foreign field." It is important to understand the concept of a foreign field. The following example shows an entry-item that is not foreign followed by an entry-item that is foreign:

// part of an order entry view

//

Object oOrderNumber is a dbForm

Set Server to oOrderhea_DD

Entry_Item Orderhea.Order_number // this is not a foreign field

:

Object oCustomerName is a dbForm

Set Server to oOrderhea_DD

Entry_Item Customer.Name // this is a foreign field

:

An example of a foreign field is the Customer.Name field when it appears in an Order Entry view. This is because it is the Order Header table that provides the server Data Dictionary; the Customer table in this example is a parent of the order header.

// part of a customer maintenance view

//

Object oCustomerName is a dbForm

set Server to oCustomer_DD

Entry_Item Customer.Name // this is not a foreign field

:

When the Customer.Name field appears in a Customer Maintenance view, it is not a foreign field because in this case the Customer table provides the server Data Dictionary.

When the Entry_Item file-number is not the same as the number of the Data Dictionary’s main file, the file should be an ancestor (parent, grandparent, etc.) table. If it is not, you have probably made an error.

Ancestor-file entry items are usually used differently from main-file entry items. When entering a new record for the main file, the parent-file records are not modified. Their role is to provide field values (customer number, etc.) on which relations are based from the main file to the parent. When the parent table must be changed (add, delete, modify) this is done in a different view, in which that table is the DDO’s main file.

Because of this, foreign fields often require additional field option settings. Although foreign-field options may be assigned to individual fields, this is little need to do this, as there is a more-convenient way to set these foreign field options. Foreign fields can be divided into three categories. Those are:

  • Key field is any field for which the Key_Field_State property is set to true (your primary key).

  • Indexed field is any field that is not a key field and provides a segment in one or more of the table’s indexes.

  • Default field is any field that is not a key field or an indexed field.

By defining foreign field options for each of these categories of fields (DD_KEYFIELD, DD_INDEXFIELD, DD_DEFAULT) your data entry applications provide the right kind entry situations with no extra programming.

Foreign-field properties are added to any regular field option properties. The following example shows a very typical use of foreign field options.

Set Field_Options field Customer.id to DD_NoPut DD_AutoFind DD_CapsLock

Set Field_Options field Customer.balance_due to DD_DisplayOnly

:

// Define default foreign field options

Set Foreign_Field_Options DD_KEYFIELD to DD_FindReq

Set Foreign_Field_Options DD_INDEXFIELD to DD_NoPut

Set Foreign_Field_Options DD_DEFAULT to DD_DisplayOnly

In this example, the regular (non-foreign field) field options for Customer.Id will be No-Put, Auto-Find and Capslock. Its foreign-field options will be No-Put, Auto-Find, Capslock and, since it is a key field, Find-Required.

All regular field-options may be set as foreign-field options. In practice the only options you are likely to set are DD_NoPut, DD_NoEnter, DD_DisplayOnly, DD_AutoFind and DD_FindReq.


Key Foreign Fields

A Key field, DD_KEYFIELD, is any field for which the Key_Field_State property is set to true. When used as foreign fields you will usually want these fields to be find-required, auto-find and no-put. This means that a parent record must be found before the main (child) can be saved, that you if enter a value in the field an auto-find will occur, and that you cannot change this key value. This foreign field setting for this will be:

Set Foreign_Field_Options DD_KEYFIELD to DD_FindReq DD_AutoFind DD_NoPut

Often the key field’s main field options have already specified auto-find and no-put, and because these standard field options are added to the foreign field options you may only need to set the following:

Set Foreign_Field_Options DD_KEYFIELD to DD_FindReq

See Also

Defining Data Dictionary Foreign Fields Attributes


Indexed Foreign Fields

An indexed field, DD_INDEXFIELD, is any field that is not a key field and provides a segment in one or more of the table’s indexes. When used as foreign fields you will usually want these fields to be enterable (so you can perform manual finds) but defined as no-put so you cannot save a changed the value. This foreign field setting for this will be:

Set Foreign_Field_Options DD_INDEXFIELD to DD_NoPut

Alternately you may decide to not even support finding on these fields, in which case your foreign field setting will be:

Set Foreign_Field_Options DD_INDEXFIELD to DD_DisplayOnly

See Also

Defining Data Dictionary Foreign Fields Attributes


Default Foreign Fields

A default field, DD_DEFAULT, is any field that is not a key field or an indexed field. You will not want to be able to change this field and since there is no index associated with this field, it can just be set to display-only. This foreign field setting for this will be:

Set Foreign_Field_Options DD_DEFAULT to DD_DisplayOnly

See Also

Defining Data Dictionary Foreign Fields Attributes


Defining Data Dictionary Parent, Child and External Relationships

Data Dictionaries need to know how to model relationships. This is accomplished by:

  • Defining all related-to parent tables

  • Defining all relation-from child tables

  • Defining any extra tables that are required

Once defined, your Data Dictionary objects can use this information to verify that DDO structures are properly configured for save and delete operations.

See Also

  • Defining Required Parent Tables

  • Defining Required Child Tables

  • Requirements for Saves

  • Requirements for Deletes

  • How DDO Structure Validation Works

  • System and External Tables


Defining Required Parent Tables

Send Add_Server_File Customer.File_Number

A Data Dictionary needs to know about its related-to, parent tables. By identifying all parent tables the Data Dictionary can determine if Data Dictionary objects have been created for all of these parents and if those parent DDOs are properly connected to the main DDO. The Parent DDOs will also have a list parent tables and these parent DDOs can also make the same check to make sure that their Parent DDOs exist and are properly connected within the structure. This process occurs up the entire database hierarchy.

By propagating this check up an entire Data Dictionary object structure any DDO can tell if it’s entire Data Dictionary structure is complete. If the structure is not complete, that DDO will not be allowed to save or delete records.

Parent table information is also used by the Studio and various wizards to help you write your applications. Therefore it is important that this information is properly maintained in your Data Dictionary class.

You add Tables to the related-parent list with the Add_Server_File message. You send this message for each required parent table.

See Also

Defining Data Dictionary Parent, Child and External Relationships


Defining Required Child Tables

Send Add_Client_File Orderdtl.File_Number

A Data Dictionary needs to know about its related-from, child tables. By identifying all child tables the Data Dictionary can determine if Data Dictionary objects have been created for all of these children and if those child DDOs are properly connected to the main DDO. The child DDOs will also have a list child tables and these child DDOs can also make the same check to make sure that their child DDOs exist and are properly connected within the structure. This process occurs down the entire database hierarchy.

By propagating this check down an entire Data Dictionary object structure any DDO can tell if it’s entire Data Dictionary structure is complete. If the downward structure is not complete, that DDO may not be allowed to delete records, as it would not have the proper child DDOs required to cascade delete related child records.

Child table information is also used by the Studio and various wizards to help you write your applications. Therefore it is important that this information is properly maintained in your Data Dictionary class.

You add tables to the related-child list with the Add_Client_File message. You send this message for each required child table.

See Also

Defining Data Dictionary Parent, Child and External Relationships


Requirements for Saves

The Data Dictionary Object structure requirement for a save is simple. Every required parent-table defined in the DDO (with the Add_Server_File method) must be represented as a DDO in the object structure and must be properly connected (with the DDO_Server property). The following code snippets show a Data Dictionary class and a valid DDO structure that would allow saves.

Procedure Define_Fields

Forward Send Define_Fields

Set Main_File to Orderhea.File_Number

:

Send Add_Server_File Customer.File_Number

Send Add_Server_File SalesP.File_Number

Send Add_Client_File Orderdtl.File_Number

End_Procedure

:

:

Object oCustomer_DD is a Customer_DataDictionary

End_Object // oCustomer_DD

Object oSalesp_DD is a Salesp_DataDictionary

End_Object // oSalesp_DD

Object oOrderhea_DD is a Orderhea_DataDictionary

Set DDO_Server to oCustomer_DD

Set DDO_Server to oSalesp_DD

End_Object // oOrderhea_DD

This is a simple case. If either of the parent DDOs had required parents those DDOs would also have to be present before a save would be allowed. For example, assume that the Customer Data Dictionary was defined as follows:

// Define_Fields in the customer DD class

Procedure Define_Fields

Forward Send Define_Fields

set Main_File to Customer.File_Number

:

Send Add_Server_File Region.File_Number

End_Procedure

The DDO structure listed above is now is incomplete. It is incomplete for Customer and therefore it is incomplete for OrderHea. Saves would not be allowed in either DDO. A proper DDO structure for this would now need to be:

Object oRegion_DD is a Region_DataDictionary

End_Object // oCustomer_DD

Object oCustomer_DD is a Customer_DataDictionary

Set DDO_Server to oRegion_DD

End_Object // oCustomer_DD

Object oSalesp_DD is a Salesp_DataDictionary

End_Object // oSalesp_DD

Object oOrderhea_DD is a Orderhea_DataDictionary

Set DDO_Server to oCustomer_DD

Set DDO_Server to oSalesp_DD

End_Object // oOrderhea_DD

Note that Add_Server_File is used to define a required parent table within a Data Dictionary and that this occurs at the class level. Using DDO_Server actually creates the connection at the object level. Add_Server_File defines the requirement and DDO_Server fulfills the requirement.

See Also

Defining Data Dictionary Parent, Child and External Relationships


Requirements for Deletes

The Data Dictionary Object structure requirement for a delete is a bit more complex. Because deletions may alter parent records, the parent DDO structure must be complete. If you wish to allow cascade deletes (the deletion of child records) then your DDO structure must also contain all required child DDOs to perform the cascade delete. For example:

Procedure Define_Fields

Forward Send Define_Fields

Set Main_File to Orderhea.File_Number

:

Send Add_Server_File Customer.File_Number

Send Add_Server_File Sales.File_Number

Send Add_Client_File Orderdtl.File_Number

End_Procedure

:

:

Object oCustomer_DD is a Customer_DataDictionary

End_Object // oCustomer_DD

Object oSalesp_DD is a Salesp_DataDictionary

End_Object // oSalesp_DD

Object oOrderhea_DD is a Orderhea_DataDictionary

Set DDO_Server to oCustomer_DD

Set DDO_Server to oSalesp_DD

End_Object // oOrderhea_DD

Object oOrderdtl_DD is a Orderdtl_DataDictionary

Set DDO_Server to oOrderhea_DD

End_Object // oOrderhea_DD

This DDO structure now contains required child DDO and deletions of OrderHea records are now allowed. However, these structures are rarely this simple. If the child DDO (OrderDtl) contained additional required parent tables then those parent DDOs and all of their parent DDOs must also be present. The required DDO Structure now might be:

Object oVendor_DD is a Vendor_DataDictionary

End_Object // oVendor_DD

Object oInvt_DD is a Invt_DataDictionary

Set DDO_Server to oVendor_DD

End_Object // oInvt_DD

Object oCustomer_DD is a Customer_DataDictionary

End_Object // oCustomer_DD

Object oSalesp_DD is a Salesp_DataDictionary

End_Object // oSalesp_DD

Object oOrderhea_DD is a Orderhea_DataDictionary

Set DDO_Server to oCustomer_DD

Set DDO_Server to oSalesp_DD

End_Object // oOrderhea_DD

Object oOrderdtl_DD is a Orderdtl_DataDictionary

Set DDO_Server to oOrderhea_DD

Set DDO_Server to oInvt_DD

End_Object // oOrderdtl_DD

See Also

Defining Data Dictionary Parent, Child and External Relationships


How DDO Structure Validation Works

When a Save or a Delete request is made to a Data Dictionary, it will validate its object structure and make sure that all of the required parent and child DDOs are present and properly connected. Depending on the type of operation, save, delete with no cascade, or delete with cascade different types of validations are performed. If the current DDO structure is not complete for the specified operation, an error will be raised and the operation will not occur.

Verifying an entire DDO structure for a particular operation can be a complicated process. As long as you provide each Data Dictionary class with an accurate list of required parent and child tables this complicated process will be handled for you automatically.

Validation Mode

Set Validate_Save_Structure_Mode to DD_VALIDATE_STRUCTURE_ONCE

Set Validate_Delete_Structure_Mode to DD_VALIDATE_STRUCTURE_ONCE

Properties are provided that allow you to modify how Save and Delete structure validation is handled. Since DDO structures tend to be static within an application you normally you only need to validate a save and delete structure one time, which will be the first time you try to save or delete within a DDO. This is the default.

If you are creating DDOs that change dynamically you may need perform this validation before every save and delete. While this is unlikely, it is supported.

Finally, you may choose to turn this validation off completely. This should be done with caution.

Below is a summary of the validation modes:

DD_VALIDATE_STRUCTURE_ALWAYS | The Data Dictionary will check the table structures every time a record is saved / deleted.
DD_VALIDATE_STRUCTURE_NEVER | Data Dictionary parent-tables-structure checking is disabled.
DD_VALIDATE_STRUCTURE_ONCE | The default. The Data Dictionary will check the table structures once when the first record is saved after loading the view. This assumes that the structure will not change during the session – normally a safe assumption.

See Also

Defining Data Dictionary Parent, Child and External Relationships


System and External Tables

Send Add_System_File Ordsys.File_Number DD_LOCK_ON_NEW_SAVE_DELETE

Certain tables should be connected to the data-server structure for which you have no reason to create a Data Dictionary object. A prime example is a system table containing a counter either for census purposes or for the purpose of assigning unique identification keys. Such tables must be registered in the Data Dictionary as an external table.

Any Data Dictionary code you write that updates a non-related table during a transaction (i.e., when the DDO is locking tables and records) should be added to the Data Dictionary class’s list of external tables. Failure to do this will raise locking errors when your code tries to commit a save to the external file.

External Tables are declared within a Data Dictionary class using the Add_System_File message.

If an External Table is used it will probably be used within one or more of the following events: Update, Backout, Creating, Deleting. Validate_Save and Validate_Delete. If you refer to an external table in these events, you will want to add this table to the external table list.

If you are using the Define_Auto_Increment command to define an auto-increment field and the table used to provide the auto-increment number is a system table, that table must be added to external table list.

Send Add_System_File Ordsys.File_Number DD_LOCK_ON_NEW_SAVE_DELETE

Define_Auto_Increment ordsys.last_cust_num to Customer.cust_number

Often system tables are not assigned a DDO. System tables represent the one exception to the Framework rule that states that a Data Dictionary subclass should be created and used for every table in an application.

Because system tables contain just one record and that record may be used by many users, an additional optimization parameter is added to determine when the external table is locked. This parameter has the following values:

DD_Lock_On_All | Lock on all saves and deletes
DD_Lock_On_New_Save_Delete | Lock on new saves and deletes
DD_Lock_On_Save | Lock on all saves
DD_Lock_On_New_Save | Lock only on new saves
DD_Lock_On_Delete | Lock only on deletes

For system tables supporting unique ID serial numbers, DD_Lock_On_New_Save should be sufficient; for tables supporting counters that increment and decrement, the other two modes would be required, so DD_Lock_On_All would be appropriate.

Note that this locking optimization is only applied to the DataFlex Embedded Database.


Defining Data Dictionary Events

Part of the Data Dictionaries class creation process is the defining of custom behaviors in events. These events allow you to customize the main Data Dictionary processes. Those processes are: Find, Clear, Save, and Delete.

Events are provided to allow you to control the following:

  • You can perform validations and error checking during a save or a delete. If an error is generated the save or delete is canceled and any changes are rolled back.

  • You can use events to maintain balances between related tables during saves and deletes.

  • You can use events to define and control handle special relationships between tables.

  • You can use events to perform special processing when new records are found. This includes setting default values for new records.

  • You can use events to perform special save or delete processing.

A single event may be used by multiple operations. For example, the Backout event is called during a save and a delete. The Relate_Main_File event may be called by saves, deletes, finds and clears.

Some events are always called in a locked state (e.g. Update and Backout) while other events may be called in a locked state (e.g. Relate_Main_File).

The predefined Data Dictionary events are:

  • Attach_Main_File

  • Backout

  • Clear_Main_File

  • Creating

  • Delete_Main_File

  • Deleting

  • Field_Defaults

  • OnNewCurrentRecord

  • Relate_Main_File

  • Save_Main_File

  • Update

  • Validate_Delete

  • Validate_Save

Forward Sending Event Messages

You should always forward event messages.

Although some of the Data Dictionary events perform no default action other events perform very important tasks and must be forwarded. Unless you specifically want to cancel a behavior, you are advised to always forward your event augmentations. This creates code that is robust – it will work the first time and it will continue to work as changes are made to your underlying classes. So, while we could code an event as follows:

// incorrect

Procedure Update

Move (Customer.due + order.total) to Customer.due

End_Procedure

We will instead code this with a forward send:

// correct

Procedure Update

Forward send Update

Move (Customer.due + order.total) to Customer.due

End_Procedure

User Defined Field Events

Data Dictionaries allow you to define events that should called when a field is validated and when windows data-entry objects bound to a particular field are entered or exited. These events are tied to fields using the Field_Validate_msg, Field_Entry_msg and Field_Exit_msg and are discussed in there own sections. In particular, the field validation events can be particularly useful.

See Also

Defining Data Dictionary Classes


Attach_Main_File

Attach_Main_File performs an attach. It gets called during save and find operations. An Attach moves related field data from a parent table to the DDO’s table. This insures that the relationship values between tables are properly maintained.

Note that this attach is only applied to parent DDOs that are connected to this DDO. If a parent table is related to the DDO’s main-file but there is no parent DDO connected to the DDO, no attach will occur.

You can use this procedure to create additional attaches, to cancel all attaches, or to create your own custom attaches. The following sample performs a normal attach except that one of the field's attaches is ignored.

Procedure Attach_Main_File

String sTempVal

Move Vndr.Parent_Stat to sTempVal // remember this value

// the normal attach will attach data from all parent files

// including an attach into Vndr.Parent_Stat. We want to

// ignore this attach

Forward Send Attach_Main_File

Move sTempVal to Vndr.Parent_State // undo this one attach

End_Procedure

Note that Attach_Main_File and Relate_Main_File are not true inverses of each other. Relate_Main_File does nothing by default (the relate occurs as part of the find). Attach_Main_File actually performs the attach command. If you do not forward, the message no attach will occur.

When accessing table values you should always access the global file buffers and not the DDO buffers (i.e., use “Move File.Field to var” syntax). See Understanding File Buffers and DDO Field Buffers for more information.

See Also

Defining Data Dictionary Events


Update and Backout

The Update and Backout events are used to maintain relational balances between tables. Update is called when a record is being saved or edited. Backout is called when a record is being edited or deleted. Update and Backout is usually used adjust the balances of parent tables. Backout is used to remove the effect of a save while Update is used to add the effect of a save.

The following Update and Backout in a checks Data Dictionary would adjust the balances of its parent, the vndr table.

Procedure Update

Forward Send Update

Move (Checks.Total+ Vndr.Total_Paid) to Vndr.Total_Paid

End_Procedure

Procedure Backout

Forward Send Backout

Move (Checks.Total - Vndr.Total_Paid) to Vndr.Total_Paid

End_Procedure

When a new record is saved, Update (and not Backout) is called.

When a record is deleted, Backout (and not Update) is called.

When a record is edited, Backout and Update are both called.

The contents of an Update and Backout will almost always be the inverse of each other. If they are not, you should carefully recheck your logic. Whatever Update adds to a balance, Backout should deduct.

It is possible that the Backout and Update might adjust different parent balances during an edit. This would happen if the edit caused the parent record to change. DDOs support this.

Do not make the assumption that Update and Backout events only get sent once during a save. While this is true in cases where parent records are not switched, it will not be true when parent records are switched. The process of maintaining relational integrity when parent records are switched is quite complicated and both events can be called multiple times for the same table (for different records).

If a change in a record should adjust balances in both a parent and a grandparent record (e.g., check detail adjusts checks total, which adjusts vendor total), it is best to let a Data Dictionary only adjust its immediate parents (e.g., check detail's Update would adjust the checks table, check’s Update would adjust the vendor table).

This message is always called in a locked state. Do not perform user input in this event.

If an Error is declared, the transaction will be rolled back. Errors must be generated using the Error command. See Error Handling in Transactions for more information.

When accessing table values you should always access the global file buffers and not the DDO buffers (i.e., use “Move File.Field to var” syntax). See Understanding File Buffers and DDO Field Buffers for more information.

See Also

Defining Data Dictionary Events


Clear_Main_File

Clear_Main_File performs a clear on the Data Dictionaries main file. Normally, you will first want to forward-send this message and then perform any type of augmentation.

If you are using this event to clear an additional table, you must tell the Data Dictionary about this by sending the message Request_Clear_File:

Procedure Clear_Main_File

Forward Send Clear_Main_File

Clear AuxFile

Send Request_Clear_File AuxFile.File_Number

End_Procedure

When accessing table values you should always access the global file buffers and not the DDO buffers (i.e., use “Move File.Field to var” syntax). See Understanding File Buffers and DDO Field Buffers for more information.

See Also

Defining Data Dictionary Events


Creating

Creating is called as part of the save process and is called when a new record is about to be saved. This event’s default behavior is to handle auto-incrementing when this is defined in the class with the Define_Auto_Increment command. For this reason, you must make sure to forward the event.

This event can be used to perform any additional processing required for a new record.

Procedure Creating

Forward send Creating

Send LogNewRecordData

End_Procedure

This message is always called in a locked state. Do not perform user input in this event.

If an Error is declared, the transaction will be rolled back. Errors must be generated using the Error command. See Error Handling in Transactions for more information.

When accessing table values you should always access the global file buffers and not the DDO buffers (i.e., use “Move File.Field to var” syntax). See Understanding File Buffers and DDO Field Buffers for more information.

See Also

Defining Data Dictionary Events


Delete_Main_File

The Delete_Main_File event performs the actual delete of the record. Delete_Main_File is intended for augmentation.

Normally, you will always want to forward-send this message. If the message is not forwarded, the delete to the database will not occur.

This message is always called in a locked state. Do not perform user input in this event.

If an Error is declared, the transaction will be rolled back. Errors must be generated using the Error command. See Error Handling in Transactions for more information.

When accessing table values you should always access the global file buffers and not the DDO buffers (i.e., use “Move File.Field to var” syntax). See Understanding File Buffers and DDO Field Buffers for more information.

See Also

Defining Data Dictionary Events


Deleting

Deleting is called during the delete process. It gets sent before the Backout message.

Procedure Deleting

Forward send Deleting

Send LogDeletedRecord

End_Procedure

This message is always called in a locked state. Do not perform user input in this event.

If an Error is declared, the transaction will be rolled back. Errors must be generated using the Error command. See Error Handling in Transactions for more information.

When accessing table values you should always access the global file buffers and not the DDO buffers (i.e., use “Move File.Field to var” syntax). See Understanding File Buffers and DDO Field Buffers for more information.

See Also

Defining Data Dictionary Events


Field_Defaults

The Field_Defaults event is provided to support the setting of default values after a clear. You may set any field value, which will be reflected in the DEOs and will get saved with the new record.

Because the setting of defaults is coded in a procedure, it allows for complex rules.

The setting of a default is not recognized as a data change by the Data Dictionary; therefore, the setting of defaults will not generate a "data-loss" warning message.

Procedure Field_Defaults

Forward Send Field_Defaults

Set Field_Changed_Value field Customer.State to "FL"

Set Field_Changed_Value field Customer.Discount to (Discount(self))

Set Field_Changed_Value field Customer.City to "Miami"

End_Procedure

Defaults may also be set upon entry of a DEO by creating an Field Entry method and it setting it with the Field_Entry_msg property. That method could set the default value using the Field_Default_Value message.

See Also

Defining Data Dictionary Events


OnNewCurrentRecord

OnNewCurrentRecord can be thought of as a post-find/clear/save/delete event since it gets called after every record find, clear, save and delete. It is called whenever CurrentRowId changes.

OnNewCurrentRecord is passed two RowId parameters: the old record RowId and the new record RowId. By looking at these two parameters, you can tell if the DDO has found a record or if it is creating a new one (if the new RowId is null).

You cannot use this procedure to change the Current RowId This message is sent to notify of a change that is going to happen. You cannot abort the change.

OnNewCurrentRecord is sent when the current record is changing, with two exceptions. If a DDO becomes in-use and the record being established is null, OnNewCurrentRecord will be called (old record =null new record =null). After a save, OnNewCurrentRecord is called for all DDOs that participated in the save, even if the record did not change. If you need to test for this condition, you could check Operation_Mode and see if it is Mode_Saving.

Procedure OnNewCurrentRecord RowId riOldRec RowId riNewRec

Forward Send OnNewCurrentRecord riOldRec riNewRec

// Assume we want to trap saves of existing records.

If (Operation_Mode = Mode_Saving AND ;

IsSameRowId(riOldRec,riNewRec) ) Begin

:

End

Else ...

End_Procedure

Special Notes

  • This message must be forwarded.

  • This event does not exist in Visual DataFlex version 10.1 and below. Instead the event New_Current_Record is used. This event is passed integer record numbers instead of RowIds. The New_Current_Record event is still called but is considered to be obsolete. OnNewCurrentRecord is the preferred replacement.

See Also

Defining Data Dictionary Events


Relate_Main_File

Relate_Main_File does nothing by default. It is called after the normal relate has been executed. It is an event that allows you to perform custom relates after your main record has been found and related.

If you are going to find an additional record in Relate_Main_File, you must notify the DDO that you have found this new record. You do this by sending the Request_Relate message (if the record was found) or Request_Clear_File (if the record was not found). Request_Relate will also perform a relate on the newly found record.

This is particularly true if the DDO is updating the parent-file DDO of the record you have found. The following code finds a "soft" parent record and notifies a DDO of the find.

Procedure Relate_Main_File

Boolean bMustFind

Integer iStatus iFile

Get Main_File to iFile

Get_Attribute DF_FILE_STATUS of iFile to iStatus

Forward Send Relate_Main_File

If (iStatus= DF_FILE_INACTIVE) begin

Move True to bMustFind

end

Else If (Vndr.Soft_id <> SoftFile.Soft_id) begin

Move True to bMustFind

end

If bMustFind Begin // relate SoftFile only if required

Clear SoftFile

Move Vndr.Soft_ID to SoftFile.Soft_Id

Find eq SoftFile.Soft_Id

End

Get_Attribute DF_FILE_STATUS of SoftFile.File_Number to iStatus

If (iStatus<>DF_FILE_INACTIVE) begin

Send Request_Relate SoftFile.File_Number

End

Else begin

Send Request_Clear_File SoftFile.File_Number

End

End_Procedure

Note the optimization of this procedure. The find in SoftFile is performed only if: (a) the SoftFile record buffer is empty; or (b) the record in the buffer is the wrong one (relating values do not match).

You are strongly encouraged to optimize your finds inside Relate_Main_File. Because DDOs have no way of knowing what this procedure is doing, they have no way of optimizing these finds. Any time a DDO thinks that the custom-related record may be incorrect, it will send Relate_Main_File. It gets sent very often. If you do not optimize your finds (a simple process), you may be slowing down your database operations.

Be aware that Relate_Main_File is not always finding the record that relates to the current record. When parent records are switched, Relate_Main_File will be called during the save process to find a related record(s) for the switched-from and switched-to records. Because of this, CurrentRowId (or the obsolete Current_Record) should not be used in Relate_Main_File procedures. Within Relate_Main_File you should refer to the actual value of the file buffer or the table’s RowId (using the GetRowId function). OnNewCurrentRecord can be used to track changes in CurrentRowId.

When accessing table values you should always access the global file buffers and not the DDO buffers (i.e., use “Move File.Field to var” syntax). See Understanding File Buffers and DDO Field Buffers for more information.

See Also

Defining Data Dictionary Events


Save_Main_File

Save_Main_File saves the main file. You can use Save_Main_File to save additional non-related tables. You can also use Save_Main_File to process additional changes in your table.

Save_Main_File gets called for every table being saved in a DD operation. This method may get called even if there are no changes to save. In such a case, the physical save to the database will not occur even though the event is called.

In this example, we want to record a date-time stamp in every saved record. Since setting a date-time field value might cause an otherwise unchanged record to get saved, we will only change the value if the record is already changed. If the file buffer is changed, we will stamp the record.

Procedure Save_Main_File

Boolean bChanged

Integer iFile

Get Main_File to iFile

Get_Attribute DF_FILE_CHANGED of iFile to bChanged

// only stamp the record if it is already changed. This assumes

// that the functions Crnt_Date and Crnt_Time already exist.

If (bChanged) Begin // if record is changed at all

Get Crnt_Date to Vndr.Date_Stamp

Get Crnt_Time to Vndr.Time_Stamp

End

// now do the normal save behavior.

Forward Send Save_Main_File

End_Procedure

Normally, you will always want to forward-send this message. If the message is not forwarded, the save to the database will not occur.

This message is always called in a locked state. Do not perform user input in this event.

If an Error is declared, the transaction will be rolled back. Errors must be generated using the Error command. See Error Handling in Transactions for more information.

When accessing table values you should always access the global file buffers and not the DDO buffers (i.e., use “Move File.Field to var” syntax). See Understanding File Buffers and DDO Field Buffers for more information.

See Also

Defining Data Dictionary Events


Validate_Delete

The Validate_Delete function is called right before a delete will occur. The participating tables are locked. Generating an error or returning a non-zero value will stop the delete.

A delete operation will delete the main file and all related descendant-file records (child, grandchild, etc.). It does this to prevent orphans. This only occurs if Cascade_Delete_State is true and there is a child-parent relationship between the tables and the DDO structure is proper.

The Validate_Delete is only sent to the DDO in whose main file the delete is being done. If Validate_Delete is approved, the main-file record and all child-file records will be deleted.

If you are prohibiting cascade deletes, it is vital that you forward the Validate_Delete message. The cascade-delete checking occurs as part of this forwarded behavior:

Function Validate_Delete returns Integer

Integer iError

Forward get Validate_Delete to iError

If (Not(iError)) Begin

If (Customer.status = 'A') Begin

Error 306 'Cannot delete active record'

End

End

Function_Return iError

End_Function

The tables of all participating DDOs are locked during Validate_Delete. Therefore, the function should be fast and must never call for user input. The exception to this is the error command. An error generated during the Validate_Delete will be deferred until after the delete has been backed out and the tables have been unlocked.

An error in any of the delete events (Validate_Delete, Deleting, Backout, Delete_Main_File, etc.) starts a transaction rollback. This is a special process. When this happens the remaining code in the function is not executed.

This message is always called in a locked state. Do not perform user input in this event.

If an Error is declared, the transaction will be rolled back. Errors must be generated using the Error command. See Error Handling in Transactions for more information.

When accessing table values you should always access the global file buffers and not the DDO buffers (i.e., use “Move File.Field to var” syntax). See Understanding File Buffers and DDO Field Buffers for more information.

See Also

Defining Data Dictionary Events


Validate_Save

Validate_Save is used as final validation of a record before saving it. It is sent to every table in the DDO structure that will participate in the save. It is called when the database is locked and all field values have been updated. You may directly query the value of any file.field buffer and if any value or combination of values is invalid, stop the save by declaring an error or by returning a non-zero value.

The data in the file buffer is exactly what would get saved. The exception to this is that the file buffer attaches have not yet occurred. This means that a child-file buffer may not contain the parent information that will get placed in the file buffer during the Attach_Main_File event. The Attach_Main_File event occurs after the Validate_Save.

The tables of all participating DDOs are locked during Validate_Save. Therefore, the function should be fast and must never call for user input. The exception to this is the error command. An error generated during the Validate_Save will be deferred until after the save has been backed out and the tables have been unlocked.

Typically, a failed Validate_Save will generate an error message, as in the example below.

Function Validate_Save returns Integer

integer iError

If (Invt.Qty < 0) Begin

Error DFERR_OPERATOR 'Insufficient Inventory on hand'

End

forward get Validate_Save to iError

Function_Return iError

End_Function

An error in any of the save events (Validate_Save, Update, Backout, Save_Main_File, etc.) starts a transaction rollback. This is a special process. When this happens the remaining code in the function is not executed.

Because the buffers for all participating tables have been updated, you should not find, clear, delete, or alter any file buffers.

This should not be confused with field validation. Field validation occurs before the database is locked and before the fields are updated to the file buffer. Field validation can be used to handle most validations. Validate_Save is used to handle special validations that can only be checked in a locked and updated state.

This message is always called in a locked state. Do not perform user input in this event.

If an Error is declared, the transaction will be rolled back. Errors must be generated using the Error command. See Error Handling in Transactions for more information.

When accessing table values you should always access the global file buffers and not the DDO buffers (i.e., use “Move File.Field to var” syntax). See Understanding File Buffers and DDO Field Buffers for more information.

See Also

Defining Data Dictionary Events


Constraints and Filters

A constraint is a restriction on what records in a Table are visible to a program component (e.g. a view, a web object, a report). There are two reasons that you might want to constrain records in a DDO:

  • When one DDO relates to another, you may want the child DDO to only show the records that relate to the current record in the related-to parent DDO. This is called a relates-to constraint.

  • A view or report may only be concerned with a subset of records at any one time. You could specify customers and a given region and/or tax bracket. These are called Filter constraints.

See Also

The Constraint Building Process

Inheritance of Constraints

Constraint Search Optimization


Relates-to Constraints

Set Constrain_File To Customer.File_Number

A relates-to constraint is used when you want to only display records in a table that are children of the record in a related-to parent DDO. The classic example of a relates-to constraint is found in an Order Entry System where a grid of order-detail records is only displays records belonging to an order-header record.

Relates-to constraints are defined by setting the Constrain_File property in a child Data Dictionary object. This is usually done statically at the DD object level. The following example would constrain all Order records to a customer record

Object oCustomer_DD Is A Customer_DataDictionary

End_Object // oCustomer_DD

Object oOrderHea_DD Is A OrderHea_DataDictionary

Set DDO_Server To oCustomer_DD

Set Constrain_File To Customer.File_Number

End_Object // oOrderHea_DD

Data Dictionaries handle relates-to constraints in a very special way. The propagation rules for finds and clears are different when a relates-to constraint is in effect. This is discussed in detail in DDO Propagation and Relate-to Constraints.

A number of requirements must be met for a relates-to constraint to operate properly:

  1. A relationship must be defined between the child and parent table. This relationship must be defined inside of Database Builder. This requires that the child table define the field or fields that relate to a corresponding set of fields in the parent.

  2. The value of the related-to field (or fields) in the parent must be unique and supported by an index whose segment (or segments) are the same as the related to field.

  3. The child table should contain one or more indexes that allow for fast finding by the relating fields. This means that the first segments in this child should consist of the relating-to fields.

  4. The Child DDO must be properly connected to the Parent DDO, by setting the DDO_Server property in the child.

Rules 1 and 2 are the standard rules used to define any parent-child relationship and is required for the relate and attach processes to work properly. Rule 3 makes it possible for DDOs to quickly find related records. It allows the DDOs to jump into an index to find the first (or last) related record and to jump out of the index when the last (or first) related record is found. While you can find related child records using other non-optimized indexes the performance with large tables will almost always be unsatisfactory.

In our above example, this would require that:

  • Our Order Table contains a field (OrderHea.Customer_number) that matches a similar field in Customer (Customer.Customer_Number).

  • The relationship is defined for Order Table within Database Builder.

  • The Customer table contains an index based on Customer.Customer_number.

  • The Order table contains at least one index whose first segment is OrderHea.Customer_Number (OrderHea.Customer_Number x OrderHea.Order_Number).

  • The Order DDO, oOrderHea_DD must define oCustomer_DD as a parent DDO using the Set DDO_Server message.

Object oCustomer_DD Is A Customer_DataDictionary

End_Object // oCustomer_DD

Object oOrderHea_DD Is A OrderHea_DataDictionary

Set DDO_Server To oCustomer_DD

Set Constrain_File To Customer.File_Number

End_Object // oOrderHea_DD

Relates-to Constraints may be applied at multiple levels. In the following example, Orders are constrained to Customers and Order-Detail items are constrained to Orders.

Object oCustomer_DD Is A Customer_DataDictionary

End_Object // oCustomer_DD

Object oOrderHea_DD Is A OrderHea_DataDictionary

Set DDO_Server To oCustomer_DD

Set Constrain_File To Customer.File_Number

End_Object // oOrderHea_DD

Object oOrderDtl_DD Is A OrderDtl_DataDictionary

Set DDO_Server To oOrderHea_DD

Set Constrain_File To OrderHea.File_Number

End_Object // oOrderDtl_DD

These types of constraints are most often used in top down data-entry and reporting. For example, you would first find a customer, then find an order that is the child of the customer, and then find an order detail that is a child of the order-detail.

Data Dictionaries support an auto-fill capability that can be used in conjunction with relates to constraints. A find in a related-to parent Data Dictionary always notify the related child Data Dictionary of this change. Since the child DDO must contain a valid constrained record it will either clear itself or, if Auto_Fill_State is true, find the first valid record. This auto-fill behavior is used to display detail grids in a data entry application.

It is a good rule to use only as many relates-to-constraints required to do the job. This kind of constraint is not needed to make saves and deletes work properly – it is only used to find records.

See Also

Constraints and Filters


Filter Constraints

Filter constraints represent all non-relational constraints that are used to filter records by any set of restrictions. Filter constraints are defined within a DDOs OnConstrain method and are specified within this method by using the Constrain command.

Object oCustomer_DD Is A Customer_DataDictionary

Procedure OnConstrain

Constrain Customer.Status eq "A"

End_Procedure

End_Object // oCustomer_DD

Filter constraints are additive. If multiple constraints are defined within an OnConstrain event all constraints must be satisfied for a record to be found. Filter Constraints will also be added to a relates-to constraint. In the following example, only orders will be found if they 1) are related to the current customer, 2) The order type is N, and 3) the shipper is fedex:

Object oCustomer_DD Is A Customer_DataDictionary

End_Object // oCustomer_DD

Object oOrderHea_DD Is A OrderHea_DataDictionary

Set DDO_Server To oCustomer_DD

Set Constrain_File To Customer.File_Number

Procedure OnConstrain

Constrain OrderHea.Shipper eq "FEDEX"

Constrain OrderHea.Type eq "N"

End_Procedure

End_Object // oOrderHea_DD

Constraints from Parent DDOs are inherited by their Child DDOs. This default behavior may not always be desired and it can be disabled by setting the pbInheritConstraints property to false.

Types of Filter Constraints

The Constrain command supports two main types of filter constraints:

The File.Field Filter

The File.Field Filter is used to specify that a table field’s value must match a specified value. And has the general format of:

Constrain {filename.fieldname} {mode} {value}

Some examples of File.Field Filters are:

Constrain OrderHea.Shipper eq "FEDEX"

Get pbLowerDate to dLowerDate

Constrain OrderHea.Date gt dLowerDate

Get psStatusConstraint to sStat

If (sStat <> "") Begin

Constrain Customer.Status eq sStat

End

File.Field filters are created and evaluated when a constraint is built and not each time a record is found. This has certain advantages. Because the constraint engine knows what field is being filtered and what type of evaluation is being applied, it is capable of determining if the constraint, given the index used for finding, can be optimized. If it can be optimized, the record search will be much faster.

The Constrain-As Filter

The Constrain-As filter is used to specify that the record much match some criteria defined by an expression. It has the general format of:

Constrain {filename} As (expression)

Some examples of Constrain-As Filters are:

Constrain Customer As (Uppercase(Customer.Name) Contains "DATA")

Constrain Customer As ( (Customer.Name Contains "DATA") Or ;

( (Customer.Type = "COMPUTER") And ;

(Customer.Area_Code Matches "3?5") ))

Constrain Customer As (TestValidity(Self))

The expression must return a true or false value. If true, the record is valid; if false it is filtered.

The expression constraint is the most flexible. You may put any expression you wish, of any level of complexity. You will usually use the expression comparisons with and/or to do this. You may also call functions within the expression. However, due to its flexibility, this is also the slowest constraint type. The expression is evaluated each time a record is found. In addition, because the constraint engine has no idea what the expression is actually doing, an expression constraint cannot be optimized (it cannot jump into or out of an index). Therefore it can be very slow in large tables. If a File.Field filter type can do the same job, it should be used.

The Constrain-As Filter can be used with other filters. If the other filters are capable of optimizing a find, the constraint engine will use those optimizations to perform index jump and jump outs. This can greatly reduce the number of records that need to be tested using the Constrain-As filters and can provide satisfactory performance.

Object oCustomer_DD Is A Customer_DataDictionary

End_Object // oCustomer_DD

Object oOrderHea_DD Is A OrderHea_DataDictionary

Set DDO_Server To oCustomer_DD

Set Constrain_File To Customer.File_Number

Procedure OnConstrain

Constrain OrderHea.Type eq "N"

Constrain OrderHea (OrderHea.Shipper="FEDEX" or OrderHea.Shipper="UPS")

End_Procedure

End_Object // oOrderHea_DD

See Also

Constraints and Filters


The Constraint Building Process

A Constraint set is usually built once and then used many times. Normally this building process occurs automatically when a view is activated or a report is run. This building process is automatic and normally you do not need to concern yourself with this.

You may have constraints that need to change based on user input or other conditions. For example, you may want users to fill in the country of the customers to deal with and then constrain the DDO to that country. To make sure this change is reflected in your DDOs you must rebuild your constraints. You do this by sending the message Rebuild_Constraints to all of the DDOs that require it.

The Rebuild_Constraints method does the following:

  1. It clears the constraint-set for the DDO receiving the message

  2. It sends OnConstrain to the receiving DDO and adds all Constrain commands to the constraint set.

  3. If the DDO’s Constrain_File property is set, it adds a Relates-To constraint to the constraint set.

  4. If the DDO’s pbInheritConstraints is True, it repeats steps 2 through 4 for all Parent DDOs.

Often after changing a constraint, you may find that your current record is no longer part of the valid constraint set. Because of this, you will often need to find a new first record after rebuilding a constraint.

In the following example, a DEO sends a message to its DDO passing two parameters, the new constraint value and the index to use for finding a new first record:

Object Customer_DD is a Customer_DataDictionary

Property String psStatusConstraint

Procedure ConstraintByStatus string sStat integer iIndex

Set psStatusConstraint to sStat

Send Rebuild_Constraints

Send Find FIRST_RECORD iIndex

End_Procedure

Procedure OnConstrain

String sStat

Get psStatusConstraint to sStat

If (sStat <> "") Begin

Constrain Customer.status eq sStat

end

End_Procedure

End_Object

See Also

Constraints and Filters


Inheritance of Constraints

Normally a DDO will inherit constraints from its parent DDOs.

A constraint is built or rebuilt when then message Rebuild_Constraints is sent to a DD object. Rebuild_Constraints creates an internal constraint "expression" that contains all of the rules for record filtering for the DDO. Under normal circumstances Rebuild_Constraints does the following:

  1. Clear the current constraints for the DDO

  2. Send the message Constrain to the DDO. This triggers the OnConstrain event.

  3. Execute all constraint commands within the OnConstrain event. Each constraint is added to the DDO's internal constraint expression.

  4. After sending OnConstrain, the constrain checks to see if a Constrain_File property is non-zero. If so a relates-to constraint is created from the DDO to the file.

  5. This process (steps 2 through 4) is then repeated for every parent DDO and this is repeated for every parent in the DDO structure.

When complete, the DDO contains a constraint expression that consists of all constraint commands defined in the main DDO and all constraint commands defined in all parents DDOs. The main DDO inherits all of the constraints of all of its parents (and grandparents, etc.).

When a record is found and parents are related, the constraint expression is run against these records. If any constraint fails, including parent constraints, the record is considered to be invalid. Therefore if a main record is found and one of its parent records contains an invalid constraint, that main record is considered to be invalid.

Most of the time this method works fine. There are times when the inheritance of constraints gets in the way. Consider an order entry system. In a typical system your detail record will relate to an order header and an Inventory parent. Assume you want to limit inventory records to items that are active (e.g., Constrain Invt.Status eq "A"). This would work perfectly for adding new orders. However, if you loaded an old order that now contains detail items for inactive inventory, those detail items will not appear in your list of order lines. This could be confusing.

In such a case, you can stop DDOs from inheriting constraints by setting the pbInheritConstraints property to false. When this happens all of your order lines would appear (because the parent constraint is not used). However, if you tried to select a different or new inventory record the inventory constraint would be enforced (because the constraint for an inventory find is evaluated by the inventory DDO.)

For example, consider an order DDO structure contains a constraint in the Inventory DDO filtering all records below a certain price. When selecting Invt records only filtered records can be selected. In the Order-detail DDO, constraints are not inherited. Therefore orders will be properly displayed with all detail records - even if the detail points to what is now an invalid Invt parent (we assume it was valid when the order was placed).

Object oVendor_DD is a Vendor_DataDictionary

End_Object // oVendor_DD

Object oInvt_DD is a Invt_DataDictionary

Set DDO_Server to oVendor_DD

Procedure OnConstrain

Constrain invt.Unit_price gt 100

End_Procedure

End_Object // oInvt_DD

Object oCustomer_DD is a Customer_DataDictionary

End_Object // oCustomer_DD

Object oSalesp_DD is a Salesp_DataDictionary

End_Object // oSalesp_DD

Object oOrderhea_DD is a Orderhea_DataDictionary

Set DDO_Server to oCustomer_DD

Set DDO_Server to oSalesp_DD

End_Object // oOrderhea_DD

Object oOrderdtl_DD is a Orderdtl_DataDictionary

Set DDO_Server to oOrderhea_DD

Set DDO_Server to oInvt_DD

Set Constrain_File to Orderhea.File_Number

// Constraints from parent (like Invt) will not be applied

Set pbInheritConstraints to false

End_Object // oOrderdtl_DD

Set Main_DD to oOrderhea_DD

Set Server to oOrderhea_DD

Placing Parents Constraints in a Child DDO

If you choose not to inherit constraints you can still place parent constraints within a child DDO. You must use the Constrain-as filter to do this. For example, your order detail DDO might contain the following:

Set pbInheritConstraints to False

Procedure OnConstrain

// The constrain file MUST be the name of DDO's main file

// but the expression can refer to a parent DDO

Constrain OrderDtl AS (Invt.Active="A")

End

Some developers may prefer using this technique because it keeps all constraints for a DDO in one place (i.e., you don't have to search the parent DDOs for additional filters.

See Also

Constraints and Filters


Constraint Search Optimization

If the DDO had to scan the entire database for every find, users could be waiting a very long time to find a record or set of records. The DDO will try to optimize the search by using the index you have programmed to find by and "jumping into" the index. Whenever a find is executed, the constraints are evaluated with the index in use to see if any optimization can be done. This is automatic—you don’t have to program anything further to make this happen.

However, if the current index can’t help the search, the DDO could have to scan the entire table. If the table has a large number of records, this may be slow. You should always test that constrained find are properly optimized for the indexes you are using (or you should at least know if the finds are not optimal). Techniques for testing this are discussed in Debugging DDOs.

The Ordering Property

Set Ordering to 3

When using constraints and filters, often only one index will provide you with proper optimized finding. When this is the case, you want to make sure you are using that best index.

You can force the DDO to always use a particular index to ensure that it finds quickly. You do this by setting the DDO’s ordering property to that index number. Normally, the value of this property is –1, which allows it to use whatever index is requested by the find message.

Object oOrderHea_DD is an OrderHea_DataDictionary

End_Object

Object oOrderDtl_DD is an OrderDtl_DataDictionary

Set DDO_Server to oOrderHea_DD

Set Constrain_File to oOrderHea.File_Number

Set Ordering to 1 // this is the only index that can be optimized

End_Object

See Also

Constraints and Filters


Creating Data Dictionary Object Structures

You use Data Dictionary classes by creating a set of Data Dictionary Objects. These objects must be properly connected and placed within an appropriate component object such as a view, web-object or business process object. This DDO object structure will handle all database processing for that component.

An application will contain many of these components; each one containing its own encapsulated set of DDOs.

Before you can use a DDO structure you must understand how to build them. This section provides that information.

Before you can build a DDO structure, you must first create the Data Dictionary class.

After you have built your DDO structure, you use them by creating DEOs and methods

A Data Dictionary object structure has a limited set of operations that it needs to perform. A DDO should be able to:

  • Find records - while properly finding all appropriate parent and child records

  • Clear records - while properly clearing or not clearing all appropriate parent and child records

  • Save records – while properly validating and saving all appropriate parent records. A save must properly save records in a multi-user environment and be able to properly roll back a transaction if a save fails.

  • Delete records – while properly validating that a delete is appropriate and allowed.

Any of these operations must operate under full control of all Data Dictionaries that participate in the operation. At all times, all the rules of DDOs must be obeyed. These rules ensure that only valid data is saved; that relational balances are properly maintained; that record IDs are properly assigned; that key field values are properly maintained; that the relationship between DDOs are always properly maintained, and more.

These rules are defined within Database Builder when you are creating and modifying your Data Dictionary classes. When you are building your Views, Reports, Web-Objects, etc. you should not be worrying about these Data Dictionary rules. You just want to know what messages are needed to perform the operation. If the operation is invalid, you expect that the Data Dictionary class will protect you.


DDO Structures

A critical part of the Visual DataFlex Framework is the understanding of how Data Dictionary objects are used to access and maintain data. This section describes how the structuring and connection Data Dictionary object controls behavior. You can do everything that is discussed in this topic visually within the Visual DataFlex Studio via the Data Dictionary Object Structure dialog. While the Studio will do much of this work for you, it is essential that you understand how it all works.

DDOs and DEOs

A Windows Application program usually contains many data-entry objects (DEOs). Some of these objects should act in a coordinated fashion with other objects. When a found record is displayed in one object, that record should be displayed in some other objects as well. Other DEOs should be independent. Changes made in one such DEO should not affect any other. The DDO is the object that coordinates activities among DEOs. All DEOs that are connected to the same DDO will act in a coordinated fashion.

DDOs also need to act in a coordinated manner with other DDOs. We accomplish this by connecting the DDOs. An interconnected group of DDOs is referred to as a Data Dictionary Object structure. Each DDO in a structure might have one or more DEOs connected to it. All of these DEOs will be coordinated. A save, delete, find, or clear operation in a DDO will affect all DEOs connected to the Data Dictionary structure in a coordinated and hopefully intelligent manner.

This leads us to the most-important rule of DDOs:

  • A DDO provides coordinated database services to every data-entry object that uses it, and to every other DDO in its Data Dictionary structure.

The above statement holds the secret to life in a DDO universe. If you want a DEO to act in coordination with another DEO, you should make sure that both DEOs use the same Data Dictionary structure. Just as important, if you do not want a DEO to act in coordination with another DEO, make sure that they are not using the same Data Dictionary structure.

A dbView is an object that contains a Data Dictionary structure together with all of the DEOs that use this structure. All of these DEOs will act in a coordinated fashion. This coordination does not come about as a result of the fact that these objects all reside in the same dbView. The coordination occurs if, and only if, the Data Dictionary structure is properly interconnected and each DEO is connected to the proper DDO. The dbView object simply hides this complexity from the outside world.

DDOs and Batch Processes

DDOs are also used outside of DEOs. For example, a BusinessProcess object may contain a DDO structure used to perform a batch update. When used in this mode, the DDOs structures are built the same way they are with DEO views. The difference will be in how the DDOs are used. Instead of having DEOs using the DDOs as their servers, methods will be created and code will be written to find, process and update data in your DDOs.

The DDO_Server Property

Set DDO_Server to oCustomer_DD

DDOs are connected within a DDO structure together with the DDO_Server message. This property, which is set in Child-Table DDOs, connects a Child-DDO to a Parent-DDO. If the child has multiple parents, it will send this message for each parent DDO. This property is set only for immediate parent DDOs. Ancestor DDOs, such as Grandparent-DDOs, should be set by their immediate child (i.e., the parent DDO sets DDO_Server of the grandparent DDO).

Object oCustomer_DD is a Customer_DataDictionary

End_Object

Object oSalesP_DD is a SalesP_DataDictionary

End_Object

Object oOrderHea_DD is a OrderHea_DataDictionary

Set DDO_Server to oCustomer_DD

Set DDO_Server to oSalesP_DD

End_Object

Object oOrderDtl_DD is a OrderDtl_DataDictionary

Set DDO_Server to oOrderHea_DD

End_Object

Building a DDO Structure

parent relationships you create in your Database Dictionary class using Add_Server_File and Add_Client_File. We will use the following table structure as an example:

A B C

\ / /

D E

\ /

F

/ \

G H

In this diagram, parent tables are above and child tables are below. Tables G and H relate to table F. table F relates to both tables D and E. Table D relates to A and B. Table E relates to table C. Tables A, B, and C have no parents.

Creating a Data Dictionary structure for these tables is quite simple.

  1. Create the parent DDOs first.

  2. Start at the top and work your way across from left to right.

  3. All connections will be handled with a set DDO_Server updating link.

The following shows how this table diagram is rendered as a Data Dictionary structure. The first step was to create a Data Dictionary subclass for each table. These classes contain all the database rules needed for these tables.

Object A_DD is a A_DataDictionary

End_Object

Object B_DD is a B_DataDictionary

End_Object

Object C_DD is a C_DataDictionary

End_Object

Object D_DD is a D_DataDictionary

Set DDO_Server to A_DD

Set DDO_Server to B_DD

End_Object

Object E_DD is a E_DataDictionary

Set DDO_Server to C_DD

End_Object

Object F_DD is a F_DataDictionary

Set DDO_Server to D_DD

Set DDO_Server to E_DD

End_Object

Object G_DD is a G_DataDictionary

Set DDO_Server to F_DD

End_Object

Object H_DD is a H_DataDictionary

Set DDO_Server to F_DD

End_Object

Connecting DEOs to a DDO Structure

Once your Data Dictionary structure is created, your DEOs must be connected to the proper DDOs. The server property of DEOs is used for this. In order to know where in a Data Dictionary structure a DEO should be connected, you must understand how DDOs and DEOs interact.

When a DEO is connected to a DDO, the DDO is referred to as the DEO's server. A DEO cannot perform any database activities itself. Instead, it will send a message to its server to perform the function. A DEO will ask a DDO to save, delete, clear, or find a record. When a DDO performs one of these operations, it will interact as needed with all other DDOs in its structure. These DDOs will interact in turn with all DEOs that are connected to them. A DDO sends two messages to a DEO. It will either ask the DEO to move data from the object to the table’s file-buffer (Entry_Update), or it will ask the DEO to move data from the file-buffer to the object (Refresh).

See Also

Creating Data Dictionary Object Structures


Propagation of DDO Operations

DDOs perform the operations Save, delete, clear/clear all and find. These operations are either initiated by a DEO sending a message to its server DDO or by custom code, which sends a message to the DDO.

Within a DEO, the operation is often initiated by users pressing a key, clicking on a button or selecting a menu item. For example, a user may press the F2 key, which will cause the Request_Save message to be sent to the DEO. The DEO will send a message to its DDO, also called Request_Save. The DDO will then perform the save.

When the programmer initiates the operation, code will be placed within a method that calls the appropriate DDO methods. This is described in Using Data Dictionary Objects in your Components.

Data Dictionary operations are performed on an entire DDO structure and never on a single DDO. This interaction is referred to as the propagation of DDO operations. It is very important to understand how the different operations cause DDOs within a structure to interact with each other. If you understand how the five major Data Dictionary operations behave, you can accurately predict how your program will behave.

  • Saves - Request_Save

  • Deletes - Request_Delete

  • Clearing - Clear and Clear_All

  • Finding – Find, Request_Find and FindByRowId

  • DDO Propagation and Relates-to Constraints

  • DDO Structures with a Single Data Dictionary

  • DDO Propagation Summary

See Also

Creating Data Dictionary Object Structures


Saves - Request_Save

A B C

\ / /

D E

\ /

F

/ \

G H

When a DDO receives a Request_Save message, it will attempt to save a record for its main table.

Saves propagate up.

This means that a save can affect the main table of its DDO and all of the DDOs above it in the updating chain.

For example:

  • If Request_Save is sent to Object A_DD, a record in table A will be saved.

  • If Request_Save is sent to Object D_DD, Tables D, A, and B will be saved.

  • If Request_Save is sent to Object F_DD, Tables F, D, E, A, B, and C will be saved.

  • What tables would be saved if Request_Save is sent to H_DD? If you answered all tables except G, you would be correct. This implies that you must have your parent-table DDOs properly connected if a Data Dictionary save is to be properly processed.

When saving via a DEO, it makes absolutely no difference what File.Field is defined by the Entry_Item command. The record that gets saved is determined by the DEO’s server DDO. If, for example, users press F2 when they are in a vendor field in a dbForm that is connected to a checks DDO, a checks record will get saved. This may also save its parent vendor record (assuming the Data Dictionary structure is correct). In this case, the Vendor dbForm would be treated as a Foreign-Field DEO.

Object oVendor_Name is a dbForm

// Because the DDO_Server is Checks, a save from here will

// save a checks record, even though the file.field value

// being displayed is Vendor which is presumably a parent

// of Checks.

Entry_Item Vendor.Name

Set DDO_Server to oChecks_DD

End_Object

If the dbForm were connected to a vendor DDO, the save would save the vendor record and not the checks record.

Object oVendor_Name is a dbForm

// Because the DDO_Server is Vendor, a save from here will

// save a vendor record.

Entry_Item Vendor.Name

Set DDO_Server to oVendor_DD

End_Object

Relates-to constraints have absolutely no effect on the save process.

See Also

Propagation of DDO Operations


Deletes - Request_Delete

A B C

\ / /

D E

\ /

F

/ \

G H

A Request_Delete attempts to delete the record from the DDO’s main table.

If the Cascade_Delete_State property is true, there are child-table DDOs in the Data Dictionary structure, and a relationship between the child and parent tables is defined in the table definition, the DDO will also delete the child records. This process will recurse down the Data Dictionary structure. Although a Request_Delete cannot delete a parent-table record, it can update a parent table. This means that a Request_Delete will propagate up and down the Data Dictionary structure.

If the DDO’s Cascade_Delete_State property is false, the DDO will not delete a record to which a record(s) in a child table(s) relates. Therefore, if a delete occurs, the propagation direction will be up (just like a save).

Deletes with cascade deletes propagate up and down.

Deletes with no cascade deletes propagate up.

For example:

  • If a Request_Delete were sent to Object H_DD, the record in table H would be deleted and Tables A, B, C, D, E, and F would be updated.

  • A delete request sent to Object A_DD would (if its Cascade_Delete_State is true) delete a record in A, delete all of A’s child records in D, delete all of D’s child records in F, and delete all of F’s child records in G and H. Deleted D records would update table B. Deleted F records would update E and C. In other words, every DDO in the table structure would be affected by a delete in A.

This seems to suggest that you must have a complete data-server dictionary in place in order to delete records in a parent table. If you want automatic deletions of child-table records, this is true. Often you will want to prohibit deletes if there are child-table records relating to the main-table record. You can use the Cascade_Delete_State property to protect against this. If you disallow deletions of records that have children, deletes will act the same as saves and only propagate up the Data Dictionary structure.

This will determine how complete your Data Dictionary structure needs to be represented inside a view. If you need a view that can save and delete records from table A (it might be a vendor table) and you are disallowing deletes if child records exist, the only DDO that will be required in your view is A_DD. If you wish to be able to delete all child records, you will need the complete Data Dictionary structure.

A few things about deletes should be emphasized. The table that a record is deleted from is determined by the DDO and not the DEO that sent the message. The record will be deleted from the receiving DDO's main table. The propagation of deletes down to child records is determined by the DDO_Server updating structure and the relationships in the tables’ definitions. If either is missing, the child deletes will not occur.

Relates-to constraints have absolutely no effect on the delete process.

See Also

Propagation of DDO Operations


Clearing - Clear and Clear_All

A B C

\ / /

D E

\ /

F

/ \

G H

The Clear_All message is simple. It clears every table in the entire DDO structure.

Clear_All propagate up and down.

The Clear message clears the Main_File of the DDO and then propagates up the DDO structure

Clears propagate up.

For example:

  • If clear were sent to A_DD, table A would be cleared. The other tables would be unaffected.

  • If clear were sent to D_DD, tables D, A, and B would be cleared. The other tables would be unaffected.

  • If clear were sent to G_DD, tables G, F, D, A, B, E and C would be cleared. Only table H would be unaffected.

The above assumes there are no relates-to constraints in place. Clear and Find propagation rule are changed by relates-to constraints. This is discussed in DDO Propagation and Relates-to Constraints.

See Also

Propagation of DDO Operations


Finding – Find, Request_Find and FindByRowId

A B C

\ / /

D E

\ /

F

/ \

G H

The primary find messages are Find, Request_Find and FindByRowId. They perform a find and a relate on parent DDOs.

Because of this relate process, finds propagate up.

For example:

  • If Find were sent to A_DD, table A would be found. The other tables would be unaffected. If a record already existed in D and you sent save to D_DD, you would be changing its parent.

  • If Find were sent to D_DD, table D would be found and tables A, and B would be found through a relate. The other tables would be unaffected. If a record already existed in F, you would be changing F’s parent.

  • If Find were sent to G_DD, table G would be found and tables F, D, A, B, E and C would be found through a relate. Only table H would be unaffected although it would now have a new parent.

The above assumes there are no relates-to constraints in place. Find and Clear propagation rule are changed by relates-to constraints. This is discussed in DDO Propagation and Relates-to Constraints.

See Also

Propagation of DDO Operations


DDO Propagation and Relates-to Constraints

The relates-to constraint is used constrain child records to a record in a parent table. Relates-to constraints change the way DDOs interact with each other. When a relate-to constraint exists, the DDO find and clear behaviors will change to operate properly within in a constrained parent-child environment. This requires that:

  1. When constrained child record is found, only records that are related to the parent will be allowed.

  2. When a constraining parent changes via a Find or Clear its record, the constrained child DDO, will be notified of the change. That child must make adhere to the constraint by either making sure that the child is within the constraint or it is cleared.

These two rules require that the propagation rules for clear and find change when a relates-to constraints is in place.

The next two samples will show the same DDO structure except that one will not have a relates-to constraints and the other will.

DDO Propagation without a Relates-to Constraint

We have a Data Dictionary structure with no relates-to constraints as shown in the diagram. Our DEOs could connect to any of these objects. For example, if our DEO were using DDO D, all of the DEO’s data-file requests (save, clear, delete, find) will be handled by DDO D.

A B C

\ / /

D E

\ /

F

Clear All

  • What will happen if Clear_All is sent to any of the DDOs?

  • All DDOs will be cleared.

Clear

  • What will happen if Clear is sent to A?

  • A is cleared.

  • What happens if Clear is sent to D?

  • D, A and B are cleared.

  • What happens if Clear is sent to F?

  • F, D, A, B, E and C are cleared.

Save

  • What will happen if Request_Save is sent to A?

  • A is saved.

  • What happens if Request_Save is sent to D?

  • D, A and B are saved.

  • What happens if Request_Save is sent to F?

  • F, D, A, B, E and C are saved.

Delete (Cascade_Delete_State is true in all DDOs.)

  • What will happen if Request_Delete is sent to A?

  • A is deleted.

  • A's children in D are deleted.

  • D's children in F are deleted.

  • B, E and C are updated.

  • What will happen if Request_Delete is sent to D?

  • D is deleted.

  • D's children in F are deleted.

  • A, B, E and C are updated.

  • What will happen if Request_Delete is sent to F?

  • F is deleted.

  • A, B, C, D, and E are updated.

Find

  • What happens if a Find for table A is sent to A?

  • A is found.

  • What happens if Find for table D is sent to D?

  • D is found and related.

  • A and B are notified of the new related records.

  • What happens if Find for table F is sent to F?

  • F is found and related.

  • A, B, C, D and E are notified of the new related records.

DDO Propagation with a Relates-to Constraint

We will give our Data Dictionary structure the following values:

A B C A = Customer

\ / / B = Terms

D E C = Vendor

c\ / D = Order Header

F E = Inventory

F = Order Detail

  1. F has a relates-to constraint to D (set Constrain_File to D.File_Number).

  2. DEO_Header_Entry uses D as its DDO server (set server to D).

  3. DEO_Table_Entry uses F as its DDO server (set server to F).

We now have a standard order-file structure. We will hook two types of DEOs to this structure: a set of order-header-entry DEOs using D as its server and an order-detail grid using F as its server. This means that users will only be sending Data Dictionary requests to DDOs D and F. Without a relates to constraint between F and D, the data-entry behaviors will not be appropriate for our entry-form/grid setup. The save and delete behaviors are fine; the clear and find behaviors are not.

We must add a relates-to constraint in DDO F which will constrain F records at any given moment to only those relating to the current record of its Parent table D. Constraints have no impact on saving or deleting (which is good). They do change the clear and find behaviors (which is also good).

Clear

  • What happens if Clear is sent to D?

  • D, A, and B are cleared.

  • D will notify its constrained child, F, that it has a new blank record.

  • F will attempt to find a record that is related to the blank child. Hopefully it will fail, clearing F, E, and C.

  • The net result is that all tables are cleared.

  • What happens if Clear is sent to F?

  • F, E and C are cleared.

  • When F attempts to clear DDO D, it will “bump” into the relates-to constraint and halt the clear up that tree branch.

Find

  • What happens if a Find for table A is sent to D or F?

  • A is found, nothing else is changed.

  • What happens if Find for table D is sent to D?

  • D is found and related.

  • A and B are notified of the new related records. In addition, D will notify its constrained child table F about the new parent record.

  • F will find the first (or last) valid record and tell the grid object about the record.

  • The grid object will fill in the grid with valid records.

  • Tables E and C are related and their DDOs are notified of the change. Therefore, when D is changed, the child grid record will change as well. This is good.

  • What happens if Find for table F is sent to F?

  • F is found only if F is a valid child for D. Therefore D, A, and B cannot change.

  • Tables E and C are related and their DDOs are notified of the change.

The header object will contain the fields allowing us to find the customer records (A). We did not choose to constrain the order header table to the customer table. For finding purposes, we do not want our search of orders to be limited to a single Customer. We did not create a separate DEO for the Customer fields. If we had done this (and that DEO was using the Customer DDO), we would have gotten undesired save and delete behaviors. Save would have only saved the Customer table. Deletes would have deleted the customer record and all of its children.

See Also

Propagation of DDO Operations


DDO Structures with a Single Data Dictionary

We will now create a view for customer entry. We will base the view on a single DDO.

A

A = Customer

What are the implications of removing the child-file DDOs? If you review our Data Dictionary-propagation rules, you will note that the only behavior that would propagate down is a delete. Saves propagate up. Since we have no parent tables, this operation will be fine. If the child-file DDOs are not provided, a delete will not delete any child records. This means that we would end up with "orphan" records (orders). Normally we do not want this. We will control this in the DDO’s Cascade_Delete_State property, making it impossible to delete customers that have child records.

How complete does your Data Dictionary structure have to be? Locate the child most-file DDO, which will have a DEO using it. Make sure that all parent-file DDOs are in your structure and properly connected. If not, your saves and deletes may not work properly. If you are disallowing deletes of records that have children, you will not need to create any DDOs below the lowest "used" DDO. If you are allowing child-record deletes, you must make sure that the child DDOs and all of those DDOs’ updated DDOs are provided and connected.

See Also

Propagation of DDO Operations


DDO Propagation Summary

It is critical that you understand how Data Dictionary operations behave and propagate. If you do, you can accurately predict how your views will behave. Below is a summary of the Data Dictionary operations and their behaviors.

Data Dictionary Operation | Normal Behavior | Relates-To Constraint Behavior
Clear_All | Clears all connected DDOs. | Same
Clear | Clears DDO and all updated DDOs. Propagates up. | Clears DDO and all updated DDOs unless a relates-to constraint is encountered. The clear up that branch will be halted. Child DDOs with a relates-to constraint are notified of the new blank record. It will clear or find as required.
Request_Save | Saves DDO record and updates all parent-file DDOs. Propagates up. | Same
Request_Delete | Deletes DDO record and updates all parent table DDOs. Child records are deleted if their DDOs update the parent DDO and Cascade_Delete_State is true. Propagates up and (conditionally) down. | Same
Finding Find, Request_Find , FindByRowId | Finds record for the requested table. A relate is performed. All DDOs are notified of the change, setting each DDO's current record (CurrentRowId) as needed. Any DEO that uses any affected DDO is refreshed. | If the table’s DDO has a relates-to constraint in effect, only records will be found that satisfy the constraint's requirements. Child DDOs with a relates-to constraint are notified of the new record. It will clear or find as required.


Diamond Structures in DDOs

Data Dictionary structures can support "diamond" relationships. A diamond relationship is one where a parent table can be reached from a child table by more than one path. Consider the following example:

A B C

\ / \ /

D E

\ / \

F I

/ \ /

G H

In this example, there are two diamond relationships (Tables B and E are the tops of each diamond). When table F finds a record, there are two paths it can use to find related Parent B ( F—>D—>B or F—>E—>B). This kind of structure is supported as long as the relationships are proper. If Record B can be reached by two paths, then you must make sure that the record that will be found in B will the same using either path. In other words, the records in D and E must have the same parent in B. Similarly, the records in F and I must have the same parent in E. As long as these rules are obeyed, diamonds are fully supported. The updating structure of your data dictionaries should reflect the diamond structure. The redundancy in updating links will not cause any problems; DDOs will take this redundancy and optimize it.

See Also

Creating Data Dictionary Object Structures


DDOs and Extended Field Support

When a DDO is created, DDO-field buffers are not created for text and binary fields. These types of fields are referred to as extended fields. Instead the DEOs deal directly with the table’s File-Buffer (reports would do this same). Binary and Text fields rarely have validation requirements so full DDO support is not really needed. If these fields are not needed, you do not want to incur the added overhead of these fields.

When working with Business Processes that edit text fields you are going to want to have extended field support in your DDOs. If you are using a Web Application, you will also want to have extended field support.

An extended DDO field can be created for any field by sending the message DefineExtendedField passing the field number of the extended field.

Extended DD fields can be created for all text and binary fields within a Data Dictionary object by sending the message DefineAllExtendedFields. Normally, you will use this message.

Object Customer_DD is a Customer_DataDictionary

Send DefineAllExtendedFields

End_Object

When an extended DD field is created, an internal field object is created for this field. Once created values are moved between the file-buffer and the extended fields in the same way they are moved in and out of the normal fields. The “refresh” process moves data from the file-buffer to the DDO-Field buffer, and the “update” process moves data from the DDO-Field buffer to the file buffer. In addition, a mechanism is provided for updating the DDO-Field buffer value.

You will only use extended fields for batch updating. When you use data entry objects (DEOs) to represent text fields, the DEO always communicates directly with the data file's buffer. If an extended field exists, it will not be used as part of the visual update.

See Also

Creating Data Dictionary Object Structures


Using Data Dictionary Objects in your Components

Data Dictionaries are implemented within an application:

  1. Creating the Data Dictionary Classes for each Table

  2. Creating Data Dictionary Object Structures in your components

  3. Using those Data Dictionary Objects in your component

This section describes the third step.

DDO structures are used either by Data Entry Objects (DEOs) or by custom methods.

When you use DEOs to work with DDOs, the interface between the DEOs and DDOs is largely hidden. In fact, you can build entire data-entry components using the Visual DataFlex Studio without having to write any code to manipulate the DDOs.

If you are accessing DDOs within methods, perhaps as part of a Business Process, Web Browser process or a Web service, you must write the code to communicate with the DDOs. The interface you will use is the same interface the DEOs use – you just have to write the code to use it yourself.

This section will concentrate on what is needed to write that code. Although all of the major DDO operations will be discussed in terms of both DEO and code based usage, most of the attention will be placed on working with DDOs with code.

Even when writing your own code, the number of commands and messages required to work with DDOs is rather small. Listed is a table of the most common table-related commands and Data Dictionary Object messages. These are the commands and messages that you are most likely to use when working with Data Dictionary objects within your application. All of these will be discussed in detail.

Command or Message | Purpose / Sample
Move File.Field to sValue | Get the unchanged value of file-buffer and move it to a variable. Often used in reports. Use this syntax within DDO events Move Customer.Name1 to sName1
Move sValue to File.Field | Seed File Buffer in preparation of a find. Move data into the File buffer. Do not use in preparation of a save. Use this syntax within DDO events. Move sName1 to Customer.Name1
Get File_Field_Current_Value | Get the current DDO field value. Do not use this in DDO events. Get File_Field_Current_Value of oCustomer_DD ; File_Field Customer.Name to sName
Set File_Field_Changed_Value | Set a DDO changed value in preparation of a save. Do not use within DDO Events. Set File_Field_Changed_Value of oCustomer_DD ; File_Field Customer.Name to sName
Send Clear | Clear the DDO and all parents unless a relates-to constraint is encountered. Send Clear of oCustomer_DD
Send Clear_All | Clear all DDOs, up and down, in the DDO structure. Send Clear_All of oCustomer_DD
Get Request_Validate | Validate all DDO fields. Return non-zero if error. Often used before a Request_Save. Tables are not locked at this point. Get Request_Validate of oCustomer_DD to bErr If not bErr begin Send Request_Save of oCustomer_DD Move (Err) to bErr End
Send Find | Find a record passing find mode and index. Find based on data in the file buffer and not on data in the DDO Field buffer. Send Clear of oCustomer_DD Move sId to Customer.Id Send Find of oCustomer_DD EQ 1 Move (Found) to bExists
Send FindByRowId | Find by record ID for passed File and RowId. Send FindByRowId of oCustomer_DD ; Customer.File_Number riRecId Move (Found) to bExists
Send Request_Delete | Delete the current record in the DDO. If failure, Err is set true. Send Request_Delete of oCustomer_DD Move (Err) to bErr
Send Request_Save | Save DDO and all parent DDOs. If failure, Err is set true. Get Request_Validate of oCustomer_DD to bErr If not bErr begin Send Request_Save of oCustomer_DD Move (Err) to bErr End
Get HasRecord | Returns true if the DDO has a current record. Get HasRecord of oCustomer_DD to bHasRecord
Get CurrentRowId | Return the DDO’s current RowId. If null, a new record. Get CurrentRowId of oCustomer_DD to riRowId
Get Should_Save | Is DDO changed – is save needed. Checks DDO and parents. Get Should_Save of oCustomer_DD to bChanged
Send Rebuild_Constraints | Sent to change the finding filters (constraints). The OnConstrain event inside of your DDOs will get called and this will define your new constraints. Send Rebuild_Constraints of oCustomer_DD Send Find of oCustomer_DD First_Record 1


Sending Messages to DDOs

Data dictionary objects are manipulated by sending them messages. These messages tend to fall into two categories:

  1. DDO Properties: These messages are sent to DDOs to either retrieve data about the DDO or to change DDO data. Often you will be getting or setting data about a field. This data might be the field’s current value (the most frequently used property message) or it might be other field data such as a field’s changed-state, its data-type, or its label name. Sometimes you will retrieve data about an entire table such as the table’s current RowId, or the table’s changed state.

  2. DDO Operations: These messages are sent DDOs to perform operations on your data. Those operations are finding, clearing, saving, deleting and validating records.

Custom methods will often combine the use of DDO property messages and DDO operation methods. For example, you might change the value of a record by 1) sending a find operation message, 2) changing a field value by sending property Set messages, 3) sending a validation operation message and 4) sending a save operation message. In all cases, these messages are sent from your custom function or procedure to the appropriate DD object.

Sending DDO Messages to an Object Handle

The mechanism for sending messages to objects is described in the Language Guide under the section titled Sending Messages to Objects in case you are not familiar with the syntax for sending messages; you are encouraged to review this section.

Before a message can be sent to a DDO, its handle must be obtained. Typically this is done by moving the object’s ID to a handle and using that variable as the object-id.

Move oSomeObject to hoHandle

Send MyMessage of hoHandle

This method works quite well within methods inside WBOs. You define a local handle variable, move your DD object handle into this variable, then send all your DDO messages using this variable. The following example demonstrates how this is done.

Function LoginUser String sCustNo String sLoginName Returns integer

Boolean bLoginOK

Handle hoDD

Move oCustomer_DD to hoDD // move object ID to handle variable

Send Clear of hoDD

Move sCustNo to Customer.Customer_Number

Move sLoginName to Customer.Login_Name

Send Find of hoDD EQ 4

Get HasRecord of hoDD to bLoginOK

Send Clear of hoDD

Function_Return bLoginOK

End_Function // LoginUser

Alternately, you send the message directly to the object as follows.

Function LoginUser String sCustNo String sLoginName Returns integer

Integer bLoginOK

Send Clear of oCustomer_DD

Move sCustNo of Customer.Customer_Number

Move sLoginName to Customer.Login_Name

Send Find of oCustomer_DD EQ 4

Get HasRecord of oCustomer_DD to bLoginOK

Send Clear of oCustomer_DD

Function_Return bLoginOK

End_Function // LoginUser

See Sending Messages to Objects for a comparison of these two techniques. You will find that the sample applications use both techniques.

See Also

Using Data Dictionary Objects in your Components


Commonly Used Data Dictionary Object Properties and Functions

When working with Data Dictionary objects you will actually use a small set of DD properties and functions. Those are the methods that allow you check the status of a DDO (is it changed, does it have a record) and allow you to get and set DDO field values. The most commonly used methods are:

  • HasRecord

  • Should_Save

  • CurrentRowId

  • Field_Current_Value

  • Field_Changed_Value

HasRecord

Get HasRecord of hoDDO to bHasRecord

HasRecord is used to determine if your DDO has a current record. This can be used to determine if a find operation succeeded (if HasRecord is true), if the DDO buffer did not contain a record prior to the find operation (otherwise, check the Found indicator).

It can also be used to determine if a save will result in a new record save (if HasRecord is false) or an edit of an existing record (if HasRecord is true).

Procedure Refresh integer iMode

Boolean bHasRecord

Handle hoDD

Forward Send Refresh iMode

// if record exists, enable the print button.

Get Server to hoDD

Get HasRecord of hoDD to bHasRecord

Set Enabled_State of oPrintBtn to bHasRecord

End_Procedure // Refresh

Should_Save

Get Should_Save of hoDDO to bChanged

Should_Save can be used to determine if a DDO has changed. If Should_Save is true, the DDO structure has changed and a save may be needed. Should_Save checks the changed state of a DDO and its parent DDOs. It also checks to see if a parent record has been switched.

There is no set variant of this message. Should_Save is set indirectly through data-input or by other messages such as Set File_Field_Changed_Value, Find, Clear, Request_Save, etc.

Get Should_Save of hoDDO to bChanged

If bChanged begin

Send Request_Validate of hoDDO to bError

If (not(bError)) begin

Send Request_Save of hoDDO

End

End

By evaluating Should_Save and HasRecord you can determine the major states of any DDO:

 | HasRecord=True | HasRecord=False
Should_Save=True | A changed record exists. A save will edit the existing record. | A new changed record. A save will create a new record.
Should_Save=False | An unchanged record exists. A save is not needed. | A new unchanged record. A save is not needed

CurrentRowId

Get CurrentRowId of hoDDO to riRec

CurrentRowId returns the RowId of the current record. If there is no current record (HasRecord=false), CurrentRowId will be null. This is often used to store the value of a current record so that it can be re-found at a later time.

Procedure RunUpdate

RowId riCust

// remember the original record

Get CurrentRowId of oCustomer_DD to riCust

// this may change the current record

Send ProcessAllCustomers

// re-find the original record

Send FindByRowId of oCustomer_DD Customer.File_Number riCust

End_Procedure

Field_Current_Value

Get Field_Current_Value of hoDD Field Customer.Name to sName

Set Field_Current_Value of hoDD Field Customer.Name to sName

Get File_Field_Current_Value of hoDD File_Field Customer.Name to sName

Set File_Field_Current_Value of hoDD File_Field Customer.Name to sName

These messages are used to retrieve and change a field’s value. Get Field_Current_Value is used to retrieve the current value of a field. This message is used frequently.

You will note that many of the samples use the file buffer to directly obtain the value of a field (e.g. Move Customer.Name to sName). This is discussed in detail in Understanding File Buffers and DDO Field Buffers.

There are two variants of the message, “Field” and “File_Field”. This is discussed in Understanding the File_Field and Field Keywords.

The set version of this message is used to change the current value of a field. You change the value of DDO field in preparation for a save. When you change a field’s value you must also change its changed-state. This can be done with the Field_Changed_State message.

Set Field_Current_Value of hoDD Field Customer.Name to sName

Set Field_Changed_state of hoDD Field Customer.Name to True

The message File_Field_Changed_Value performs both of these operations and is usually used for this purpose. Therefore, the Set File_Field_Current_Value message is rarely used.

Important

If a save operation is not changing the value of changed field, check if you are using Field_Current_Value instead of Field_Changed_Value. This is a common mistake.

Field_Changed_Value

Set Field_Changed_Value of hoDD Field Customer.Name to sName

Set File_Field_Changed_Value of hoDD File_Field Customer.Name to sName

This message is used to change the current value of a field and to set the field’s changed state to true. This is a commonly used message and is the suggested method for changing DDO field values in preparation for a save. This message is only used for changing values. There is no get version of this message.

Function ChangeName RowId riRecId string sName returns Boolean

Boolean bErr

Send Clear of oUser_DD

Send FindByRowId of oUser_DD User.File_Number riRecId

If (Found) Begin

// update name and save

Set Field_Changed_Value of oUser_DD field User.Name to sName

Get Request_Validate of oUser_DD to bErr

If (not(bErr)) begin

Send Request_Save of oUser_DD

// if save succeeds, Err indicator is False

Move (Err) to bErr

End

End

Else Begin

Move True to bErr

End

Function_Return bErr

End_Function

You will note that many of the samples use the file buffer to directly set the value of a field (e.g. Move sName to Customer.Name). This is discussed in detail in Understanding File Buffers and DDO Field Buffers.

There are two variants of the message, “Field” and “File_Field”. This is discussed in Understanding the File_Field and Field Keywords.


Understanding Relates and Attaches

Data Dictionary objects perform relates and attaches to maintain your hierarchy of related records. Relates involves the finding of a related-to records in parent tables. Attaches involve the moving of related field data from a parent table to a child table. The relate and attach processes are mostly automatic. As long as your relationships are defined for your tables and your Data Dictionary classes, and your DDO structure is properly assembled, the Data Dictionaries will perform these operations at the proper time and in the proper manner.

The Attach Process

Attaches are performed before a save or a find. Within the DDO structure, each DDO that will participate in the save or find will be sent the message Attach_Main_File. This event attaches data by moving all related file buffer data from the parent table to the child table. The attach will only occurs if the parent DDO is part of the DDO structure.

There is often confusion about how attaches are used in a find resulting in finds appearing not to work. A find is based on the contents of the table’s file-buffer. Right before the actual find, Attach_Main_File is sent. As mentioned, this moves field data from the parent to the child. If your child file-buffer contained finding data in those fields they will be replaced with the field data from the parent. This means that the parent table file-buffers and not the child table file-buffers should be seeded with related data before a find.

For example, assume you wish to perform a find an Order table in Customer Number x Order Number sequence – presumably on an index that is OrderHea.Customer_Number x OrderHea.Order_Number.

Object oCustomer_DD is a Customer_DataDictionary

End_Object

Object oOrderHea_DD is an OrderHea_DataDictionary

Set DDO_Server to oCustomer_DD

End_Object

:

// This will not work properly.

// Customer number is moved into the OrderHea File buffer

// and will be lost when the attach occurs before the find

Procedure FindNextOrder integer iCustomer integer iOrder

Send Clear of oOrderHea_DD

Move iCustomer to OrderHea.Customer_Number

Move iOrder to OrderHea.Order_Number

Send Find of oOrderHea_DD GT 2

End_Procedure

:

// This will work properly.

// Customer number is moved into the Customer File buffer

// and will be attached before the find

Procedure FindNextOrder integer iCustomer integer iOrder

Send Clear of oOrderHea_DD

Move iCustomer to Customer.Customer_Number

Move iOrder to OrderHea.Order_Number

Send Find of oOrderHea_DD GT 2

End_Procedure

The same applies to the Binding of a File.Field value in a DEO. You should always use the related-parent file.field and not the child

// This will not work properly.

Object oCustomerNumber is a dbForm

Entry_Item OrderHea.Customer_Number

End_Object

// This will work properly.

Object oCustomerNumber is a dbForm

Entry_Item Customer.Customer_Number

End_Object

Note that the parent to child attach only occurs if the parent DDO is part of the structure.

The Attach_Main_File event is discussed under Defining Data Dictionary Classes. Most of the time augmentations of this message will occur at the class level and will be used to model a custom relationship.

If you are augmenting Attach_Main_File at the object level you should be particularly careful about the save operation. The Attach_Main_File called right before a record is saved, is used to ensure that the child record contains the proper related field data. If you alter this, you could compromise database integrity.

It is possible to perform different attach actions for saves and find. You do this by checking the operation-mode of the DDO. This is done with the Operation_Mode global integer. In this sample, we will disable attaches for all finds but enable them for saves.

Object oOrderHea_DD is an OrderHea_DataDictionary

// Disable attaches for finds but perform normal attaches with saves

Procedure Attach_Main_File

If (Operation_Mode=Mode_Saving) begin

Forward send Attach_Main_File

End

End_Procedure

End_Object

The Relate Process

A relate occurs after any successful find. All related parent records are found and all parent DDOs are updated with the new record. This relate is internal – no message is sent to perform the actual relate. The relate process insures that a find updates you entire DDO structure. The main record and all of it’s parents, grand-parents, etc. will be found and loaded into your DDO structure.

Disabling Relates

The internal relate can be disabled by setting the No_Relate_State property to true. This would rarely be done at the class level, but might be used at the object level to improve finding speed. For example, you may need to search through a table but have no need to find all parent records.

Procedure FindRecord

Handle hoDDO

Boolean bTemp bFound

Move oOrdeHea_DD to hoDDO

Get No_Relate_State of hoDDO to bTemp

Set No_Relate_State of hoDDO to True

Send Find of hoDDO FIRST_RECORD 1

Move (Found) to bFound

While bFound

Send ProcessRecord

Send Find of hoDDO GT 1

Move (Found) to bFound

End

Set No_Relate_State of hoDDO to bTemp

End_Procedure

Custom Relationships

After every find and relate the event Relate_Main_File is sent to every DDO that participated in the find. This is sent even if No_Relate_State is set True. This event allows you to model custom relationships. These kinds of custom relationships are almost always modeled in your class and not coded at the object level. See Relate_Main_File in Defining Data Dictionary Events for more on using this event.

See Also

Find and Clear Operations in DDOs


Save Operations in DDOs

A DDO save is normally a two-step process. First you send Request_Validate to the DDO to make sure that your data is ready for the save. If your data is valid, you then send Request_Save to the DDO to perform the save.

In some cases, you will use objects such as data-entry object (DEOs) to handle the entire save for you. In other cases, you will need to write custom code to perform the save. In all cases, the save process itself is the same.

The Request_Validate Message

Get Request_Validate of hoDD to bErr

Before a Request_Save is sent to a DDO, the Request_Validate message should be sent to validate that all data is valid for a save. When performing saves with a DEO or using a Web Browser object’s high level save, this message is sent for you. If you are performing saves manually within a Batch Process or Web Object, you will need to send this message yourself.

Request_Validate sends validation messages to all DDO Field’s that will participate in the upcoming save. If the validation of any field fails, an error is reported and the save will not proceed.

Function SaveCustomer returns Boolean

Boolean bErr

Get Request_Validate of oCustomer_DD to bErr

If Not bErr Begin

Send Request_Save of oCustomer_DD

Move (Err) to bErr

End

Function_Return bErr

End_Function

It is important that you understand when and how this process occurs.

  • Before a save, every single field in every single DDO that will participate in the save will be validated.

  • This is a pre-save validation.

  • Your database is not locked at this point.

  • If any validation function returns a non-zero value, the Request_Validate will fail and return a non-zero value.

  • An error message will usually be generated when a field validation fails.

  • If DEOs are attached to the DDO, the DDO will attempt to give the focus to the DEO that contains the invalid field data.

  • This field validation is a different process than the actual DDO save validation (Validate_Save), which occurs much later in the save process.

The Request_Save Message

Send Request_Save of hoDD

When the save operation is performed by a DDO, it saves records into its main table as well as the tables in its parent DDOs. This allows updates in total fields in related tables to be performed as each record in the main table is created or edited. This action can also cause the creation of new records in any of the tables. Any record buffer that was not pointing to an already-existing record will have a new record created.

Function SaveCustomer returns Boolean

Boolean bErr

Get Request_Validate of oCustomer_DD to bErr

If Not bErr Begin

Send Request_Save of oCustomer_DD

Move (Err) to bErr

End

Function_Return bErr

End_Function

The Request_Save message is responsible for saving new or existing records. When Request_Save is sent to a DDO, it will:

  • Verify that the DDO structure is valid for the save operation.

  • Begin Transaction – From this point on tables are locked (if the database uses table locking) or records are locked as they are found (if the database uses record locking).

  • Re-find all original records. Send Backout to all tables with existing records.

  • If any parent records are switched (i.e., you selected a different parent), save the original records (with backed out data) and re-find the new records.

  • Move all changed data from the DDO-Field buffers to the table file-buffers.

  • Send Creating to all DDOs with new records.

  • Send Update to all participating DDOs.

  • Get Validate_Save of all participating DDOs. If any Validate_Save fails, cancel the operation.

  • If all DDOs validate, attach, and save all records by sending Attach_Main_File and Save_Main_File to every table.

  • End Transaction – either commit changes or roll back changes. Unlock tables.

  • If save was successful, send Refresh all participating DEOs.

Handling Errors

If an error occurs anywhere within a save, the transaction is rolled back, the database is unlocked and the error is reported.

Most often the error will occur within the Validate_Save event and will be a “controlled” error (i.e., an error purposefully created by the programmer). This must be a real DataFlex error generated using the Error command. The generation of an error is what triggers a transaction rollback. The reporting of the error will be properly deferred until the transaction is rolled back and your database is unlocked.

Although Validate_Save is the event specifically created to allow you to perform one last validation, you may stop a transaction within any of the save events (Creating, Update, Backout, Attach_Main_File, Save_Main_File). If you generate an error, the transaction will be rolled back.

Propagation of Save Events

Unless otherwise noted, the messages listed are sent to all the objects in a data-server structure that will participate in a particular operation. Validate_Save, for example, gets called during a save operation. A save involves the DDO that actually started the save (received the Request_Save message) and all the DDOs that it updates (saves propagate up). During a save, Validate_Save is sent to every one of these objects. When you think of DDO behaviors, try to think in terms of the behaviors of Data Dictionary structures and not single DDOs.

When a message is propagated through a Data Dictionary structure, the order in which messages are sent will vary. In some cases, a message will be sent to the parent-most (server) DDO and propagate down to the child DDOs (clients). The Save_Main_File message propagates in this manner. Other messages will start with the child and propagate up to the parents. The update messages propagate in this manner. Usually all you need to know is that the message will get sent to the right DDOs in the right order based on the function of the message.


Save Operations with Data Entry Objects

When DEOs are connected to a Data Dictionary Object this entire save process is automatic and controlled by the user. The user will enter data, find records and then perform some action (button click, keystroke, etc.) to request the save. When this occurs a message is sent to the data-entry, usually Request_Save, to start the save. The DEO does all of the work for you – it:

  • Checks if a save is needed

  • Asks its DDO to validate all fields (Request_Validate)

  • Verifies with the user that they want to perform the save

  • Asks its DDO to perform the save (Request_Save)

  • Updates DEOs with new saved changes

  • Optionally, clears the DEOs

Below is an example of a simplified DEO Request_Save procedure. These are the types of methods and functionality built directly into DEOs.

// The DEO save process

procedure Request_Save

Handle hoDDO

Boolean bError bClear

Get Server to hoDDO

Get Should_Save of hoDDO to bChanged

If (not(bChanged)) Procedure_Return // nothing to save

Get Request_Validate of hoDDO to bError

If (bError) Procedure_Return // field validation error, can't save

Get Verify_Save to bError // ask user to verify save

If (bError) Procedure_Return // user decided to not save

Send Request_Save of hoDDO

Move (Err) to bError // Err set if save failed

If (not(bError)) begin

Get Auto_Clear_DEO_State to bClear // clear after save?

If bClear send Request_Clear // ask DEO to clear

End

End_Procedure

See Also

Save Operations in DDOs


Save Operations within BPOs and WBOs

Business Processes and Web Business Processes often require that you write your own save processes. This section will explain how to do this.

You will use the Request_Validate and Request_Save messages to save records.

A manually coded save operation consists of the following steps:

  1. If editing, find a Record. If adding a new record, clear the DDO.

  2. If it’s a new record, find any parent DDO records as needed.

  3. Update all DDO values using Set File_Field_Changed_Value.

  4. Check that all fields in all participating DDOs are valid, using Request_Validate.

  5. Tell the DDO to save the record using Request_Save.

  6. Check the status of the save and react accordingly.

The following example shows a typical save operation for a new record.

Function NewOrder Integer iCustNo String sShip Returns Integer

Integer iOrderNo

Boolean bErr

Handle hoOrderDD hoCustDD hoSalesPDD

Move oOrderHea_DD to hoOrderDD

Move oCutomer_DD to hoCustDD

Move oSalesP_DD to hoSalesPDD

// Step 1: clear all DDOs

Send Clear of hoOrderDD

// Step 2: Find all required parent records

Move iCustNo to Customer.Customer_Number

Send Find of hoCustDD EQ 1

Move "INET" to SalesP.Id

Send Find of hoSalesPDD EQ 1

// Step 3: Update DDO with changed values

Set Field_Changed_Value of hoOrderDD Field OrderHea.Terms to "COD"

Set Field_Changed_Value of hoOrderDD Field Orderhea.Ship_Via to sShip

// Step 4: Validate all fields

Get Request_Validate of hoOrderDD to bErr

// Step 5: If no error, save new record

If (not(bErr)) Begin

Send Request_Save of hoOrderDD

// if save failed, the Global Err indicator is set True

Move (Err) to bErr

end

// Step 6: Post save processing

If (Err) Begin

Move 0 to iOrderNo

End

Else Begin

Move OrderHea.Order_Number to iOrderNo

end

Function_Return iOrderNo

End_Function

The messages Request_Validate and Request_Save are almost always used in this fashion. First you must verify that all fields are valid. If the fields are valid, you save the record. If you do not include the Request_Validate as part of a save, it is possible to save records with improperly validated data. Why do we even allow this condition? There may be times when a programmer is sure that a change in a process is valid, and may wish to bypass the overhead of a validation. The choice is yours – if you bypass validations, be careful. The following example clears the last_access date field in all records. If we are quite sure that this is a valid operation, we may skip the validation as follows:

Function ClearUserDates returns integer

Handle hoDD

Move oUser_DD to hoDD

Send Clear of hoDD

Send Find of hoDD Ge 1

While (Found)

Set Field_Changed_Value of hoDD Field User.Last_access to 0

Send Request_Save of hoDD

Send Find of hoDD GT 1

Loop

End_Function

Save errors can occur in two places:

  • If the return value from Request_Validate is non-zero, the field validation failed.

  • If the Err global indicator is returned true after a Request_Save, an error occurred during the save and the transaction was rolled back. This error might have occurred inside the Validate_Save event, any of the other save events or an unexpected error may have occurred.

As a final example, this function from a WebApp shopping cart sample where we will change the quantity of an order detail item. This would be called within a Web Browser object and the message, IncOrderDtl, will have been registered as an external interface message.

// This function is called by a button from the shopping cart

// and increase the quantity of an item by one

Function IncOrderDtl Integer iOrderNo Integer iDetailNo Returns Integer

Boolean bErr

Integer iTmpQty

Move oOrderDtl_DD to hoDtlDD

Send Clear of hoDtlDD

Move iOrderNo to Orderdtl.Order_Number

Move iDetailNo to Orderdtl.Detail_Number

Send Find of hoDtlDD EQ 1

If (Found) begin

Move Orderdtl.Qty_Ordered to iTmpQty

Increment iTmpQty

Set Field_Changed_Value of hoDtlDD Field Orderdtl.Qty_Ordered to iTmpQty

Get Request_Validate of hoDtlDD to bErr

If not bErr Begin

Send Request_Save to hoDtlDD

Move (Err) to bErr

End

end

Function_Return bErr

End_Function

This could be called from an ASP page as follows:

<%

If shopAction = ">" then

bErr = oOrder.Call("Get_IncOrderDtl", iOrderNo, iDetailNo)

end if

%>

See Also

Save Operations in DDOs

If you are writing a web application, see Sessions and Persistence


Delete Operations in DDOs

A DDO delete is performed by send Request_Delete to a DDO. In some cases, you will use objects such as data-entry object (DEOs) to handle the entire delete for you. In other cases, you will need to write custom code to perform the delete. In all cases, the delete process itself is the same.

The Request_Delete Message

The Request_Delete message is responsible for deleting a record in the DDO for the main table, updating all parent DDOs (and tables) and possibly deleting all child records.

Two modes of deleting are supported:

  • A delete will delete all related descendant records.

  • A deletes will not be allowed when child records exist.

The property Cascade_Delete_State controls this message.

Deleting a Record and all of its Children

If Cascade_Delete_State is true, Request_Delete will attempt to delete the DDO’s record and all descendant records as follows:

  • Verify that the DDO structure is valid for the cascade delete operation.

  • Begin Transaction – From this point on tables are locked (if the database uses table locking) or records are locked as they are found (if the database uses record locking).

  • Re-Find all required records.

  • Get Validate_Delete of the main DDO.

  • if validation fails, cancel operation

  • Delete all related descendant records. For each child record:

  • Send Deleting to the child DDO.

  • Send Backout to all parent DDOs.

  • Attach and Save all parent tables (Attach_Main_File, Save_Main_File)

  • Delete the child record (Delete_Main_File)

  • Delete the record of the main DDO

  • Send Deleting to the main DDO.

  • Send Backout to all parent DDOs.

  • Attach and Save all parent tables (Attach_Main_File, Save_Main_File)

  • Delete the main record (Delete_Main_File)

  • End Transaction – either commit changes or roll back changes. Unlock tables.

  • If delete was successful, send Refresh all participating DEOs.

Deleting a Record only when no Children Exist

If Cascade_Delete_State is false, Request_Delete will not allow users to delete a record when a child-file record(s) exists as follows:

Deletes when Cascade_Delete_State is false:

  • Verify that the DDO structure is valid for the no-cascade delete operation.

  • Begin Transaction – From this point on tables are locked (if the database uses table locking) or records are locked as they are found (if the database uses record locking).

  • Re-Find all required records.

  • Get Validate_Delete of the main DDO.

  • if validation fails, cancel operation

  • Check for any child records or closed tables (get Validate_Delete_No_Cascade)

  • if validation fails, cancel operation

  • Send Deleting to the main DDO.

  • Send Backout to all parent DDOs.

  • Attach and Save all parent tables (Attach_Main_File, Save_Main_File)

  • Delete the main record (Delete_Main_File)

  • End Transaction – either commit changes or roll back changes. Unlock tables.

  • If delete was successful, send Refresh all participating DEOs.

Handling Errors

If an error occurs anywhere within a delete, the transaction is rolled back, the database is unlocked and the error is reported.

Most often the error will occur within the Validate_Delete event and will be a “controlled” error (i.e., an error purposefully created by the programmer). This must be a real DataFlex error generated using the Error command. The generation of an error is what triggers a transaction rollback. The reporting of the error will be properly deferred until the transaction is rolled back and your database is unlocked.

Although Validate_Delete is the event specifically created to allow you to perform one last validation, you may stop a transaction within any of the delete events (Deleting, Backout, Delete_Main_File, Attach_Main_File, Save_Main_File). If you generate an error, the transaction will be rolled back.


Delete Operations with Data Entry Objects

When DEOs are connected to a Data Dictionary Object this entire delete process is automatic and controlled by the user. The user will find a record and then perform some action (button click, keystroke, etc.) to request the delete. When this occurs a message is sent to the DEO, usually Request_Delete, to start the delete. The DEO does all of the work for you:

  • Checks if a delete is needed and allowed

  • Verifies with the user that they want to perform the save

  • Asks its DDO to perform the save (Request_Delete)

  • Updates DEOs with new saved changes

  • Optionally, clears the entry forms

Below is an example of a simplified DEO Request_Delete procedure. These are the types of methods and functionality built directly into DEOs.

// The DEO Delete Process

Procedure Request_Delete

Handle hoDDO

Boolean bCanDelete bError

Get Server to hoDDO

Get Can_Delete of hoDDO to bCanDelete

If (not(bCanDelete)) Procedure_Return // deletes are not allowed

Get Verify_Delete to bError // ask user to confirm delete

If bError Procedure_Return // user decided not to delete

Send Request_Delete of hoDDO

Move (Err) to bError // Err set if delete failed

If (not(bError)) Begin

Send Request_Clear // clear DDOs after the delete

end

End_Procedure

See Also

Delete Operations in DDOs


Delete Operations within BPOs and WBOs

Business Processes and Web Business Processes often require that you write your own delete processes. This section will explain how to do this.

A manually coded delete operation consists of the following steps:

  1. Find the record you wish to delete

  2. Delete the record by sending Request_Delete to the DDO

  3. Check the status of the delete and react accordingly.

The following example shows a typical delete operation:

Function DeletePerson string sID returns integer

Handle hoDD

Boolean bErr

Move oSalesP_DD to hoDD

// Step 1: Find the record to delete

Send Clear of hoDD

Move sID to SalesP.Id

Send Find of hoDD EQ 1

If (not(Found)) Function_Return 1 // error

// Step 2: delete the record

Send Request_Delete of hoDD

Move (Err) to bErr

// Step 3: Check status of delete and react

If not bErr Send Clear of hoDD

Function_Return bErr

End_Function

If a delete fails, the Err global indicator is set to true.

See Also

Delete Operations in DDOs


Find and Clear Operations in DDOs

The find and clear operations are discussed in the same section because they have much in common – in fact, one could argue that a clear is simply a special type of find. Finds and clears follow the same propagation rules and they treat relates-to constraints in a similar manner.

The messages used to Clear DDOs are Clear and Clear_All.

Send Clear of hoDDO

The primary find message is Find. It performs a find based on the contents of the file-buffer, performs a relate on parent DDOs and updates all affected DEOs

Send Find of hoDDO GT 1

Additional messages used for finding are Request_Find, FindByRowId, Find_By_Recnum, and Request_Assign.


Finds, Constraints and Filters

Constraints in DDOs determine how records are filtered and how finds are propagated. Constraints and Find Filters are discussed in Constraints and Filters. A short review of is presented here.

Relates-to Constraints

Relates-to-constraints are used to constrain the display of child records to a record in a related to parent.

Constrain_File: Setting the Constrain_File property in the child DDO creates a relates-to-constraint.

When a relates-to-constraint is in place the propagation rules of finds and clears are changed (saves and deletes are unchanged). Propagation is changed as follows:

  1. The finding of child records is limited to records that are related to the parent

  2. Clearing a child DDO will not clear the constraining parent DDO.

  3. Finding a constraining parent record will send a notification to the constrained child DDO. The child must either find a valid constrained record or clear itself.

  4. Clearing a constraining parent record will send a notification to the constrained child DDO forcing it to clear.

Two properties, Auto_Fill_State and pbAutoFillFromFirst, determine how a constrained child should react to a changed parent.

Auto_Fill_State: If Auto_Fill_State is false, changing a constraining parent record will force the child DDO to clear itself. If Auto_Fill_State is True, changing a constraining parent record will force the child to find its first or last record.

Auto_Fill_State only takes effect when a relates-to constraint is in effect.

pbAutoFillFromFirst: This property determines if an auto-fill should find the first or last constrained record. When true, the default, the first constrained child record is found; when false the last constrained record is found. This only takes effect when Auto_Fill_State is true and a relates-to constraint is in effect.

Filtered Constraints

Filter constraints represent all non-relational constraints that are used to filter records by any set of restrictions. Filter constraints are defined within a DDO’s OnConstrain method and are specified within this method by using the Constrain command.

There are two basic types of filters – The File.Field Filter and the Constrain-As Filter.

Building Constraints

A Constraint set is usually built once and then used many times. Normally this building process occurs automatically when a view is activated or a report is run. If you change your constraints you can rebuild constraints manually using by sending the Rebuild_Constraints message.

Inheritance of constraints specifies if child DDOs should use their parent constraints and filters. When a constraint set is built for a DDO it normally inherits the constraints from its parent DDO and all of their ancestor DDOs. This can be disabled by setting the pbInheritConstraints property to false.

Constraint Optimization

When finding using constraints and filters you want to make sure that the finding will be properly optimized. This normally, requires that you build a constraint that is capable of using an index that can be jumped in and out of.

If your constrained find only has one index that can be used in an optimized fashion you should make sure you use that index in your finds. You can force a DDO to use a particular index by setting the Ordering property.

See Also

Find and Clear Operations in DDOs


Clear and Clear_All

You will use the Clear or Clear_All messages to clear DDOs. The Clear message will clear the DDO and all connected parent DDOs. If parent DDOs have a relates-to constraint between them the clear activity will be blocked by the constraint (i.e. the parent and any of its parents will not be cleared).

Clear_All unconditionally clears all DDOs in a structure.

After a Clear or Clear_All any attached DEOs will be notified of the change.

A Clear performs the following steps:

  • Clear main DDO and all Parent DDOs on up the Parent DDO tree (Clear_Main_File).

  • If a relates-to-constraint is in effects stop the upward clear propagation

  • If a related-to child exists, clear the child (Clear_Main_File) and all of its parents.

  • Send OnNewCurrentRecord to all cleared DDOs

  • Send Refresh to all DEOs attached to DDOs that were cleared

A Clear_All performs the following steps:

  • Clear every DDO in the structure (Clear_Main_File).

  • Send OnNewCurrentRecord to all cleared DDOs

  • Send Refresh to all DEOs attached to DDOs that were cleared

A clear operation may be programmed within a Web Object or Business Process object as follows:

Function ClearUser Returns integer

Handle hoDD

Move oCustomer_DD to hoDD

Send Clear of hoDD

End_Function // SearchUser

The clear operation is often combined with other operations.

Function FindUser String sCustNo Returns RowId

RowId riRecId

Handle hoDD

Move oCustomer_DD to hoDD

Send Clear of hoDD

Move sCustNo to Customer.Customer_Number

Send Find of hoDD EQ 1

Get CurrentRowId of hoDD to riRecId

Function_Return riRecId

End_Function // SearchUser

Clears and DEOs

The above sample shows how to use clears in batch processes. The Clear and Clear_All operations are built into Data Entry Objects allowing the user to direct the operation. A Clear (or Clear_All) is started when the user a presses a key (F5), clicks on a button or selects a menu option. This sends the message Request_Clear (or Request_Clear_All) to the DEO. The DEO will then

  • Checks if any changes exist, and if they do, verify a data-loss condition

  • Send Clear (or Clear_All) to the DDO

Below is an example of a simplified DEO Request_Clear procedure. These are the types of methods and functionality built directly into DEOs.

// The DEO Clear Process

Procedure Request_Clear

Handle hoDDO

Boolean bChanged bError

Get Server to hoDDO

Get Should_Save of hoDDO to bChanged // are there changes?

If bChanged begin

Get Verify_Data_Loss to bError // do we really want to clear?

If bError Procedure_Return // user decided not to clear

End

Send Clear of hoDDO // this will clear and notify all DEOs

End_Procedure

See Also

Find and Clear Operations in DDOs


Find

You will normally use the Find message to find a record and all of its parents. A typical find operation can be described as follows:

  1. Prepare your File Buffer with properly seeded Field values. Note that you prepare the File Buffer and not DDO-Field values (see Understanding File Buffers and DDO Field Buffers).

  2. Send the Find message to the DDO, passing the find mode and find index as parameters.

  3. Check status of the find and react accordingly.

A Find actually sends the Request_Find message passing the mode, file-number and index. Request_Find does the actual find performing the following steps:

  • Perform a parent to child attach for all parents (Attach_Main_File)

  • Perform the find based on constraints and filters

  • If the find is successful, perform a relate for all parents

  • Perform custom relates by sending Relate_Main_File to all DDOs

  • If DDO has a related-to child, inform the child DDO of the new record

  • The Child DDO will clear or find as needed

  • Send OnNewCurrentRecord to all DDOs with newly found or cleared records.

  • Send Refresh to all DEOs attached to DDOs

Note that all DEO objects attached to the DDO performing a find will immediately display the results of a find.

When DEOs perform finds they use the Request_Find message. When performing finds in your code you will usually the Find message. The examples shown here show how to use the Find messages for manually coded operations.

The following example shows a typical find operation:

Function FindUser String sCustNo Returns Boolean

Boolean bFound

Handle hoDD

Move oCustomer_DD to hoDD

// Step 1: Prepare the File Buffers for a find

Send Clear of hoDD

Move sCustNo to Customer.Customer_Number

// Step 2: Perform the find Operation

Send Find of hoDD EQ 1

// Step 3: Process the information after the find.

Move (Found) to bFound

Function_Return bFound

End_Function // SearchUser

The Found indicator is always set after a find. Because this indicator is global and easily changed you should always move the status of this indicator to some other variable.

You can also test if a find succeeded by checking the DDO’s HasRecord function if the DDO buffer did not contain a record prior to the find operation (otherwise, check the Found indicator).

It can also be used to determine if a save will result in a new record save (if HasRecord is false) or an edit of an existing record (if HasRecord is true).

Send Find of hoDD EQ 1

Get HasRecord of hoDD to bFound

In some cases, you may wish to return the value of the record’s RowId.

Function FindUser String sCustNo Returns RowId

Rowid riRecId

Handle hoDD

Move oCustomer_DD to hoDD

// Step 1: Prepare the File Buffers for a find

Send Clear of hoDD

Move sCustNo to Customer.Customer_Number

// Step 2: Perform the find Operation

Send Find of hoDD EQ 1

// Step 3: Process the information after the find.

Get CurrentRowId of hoDD to riRecId

Function_Return riRecId

End_Function // SearchUser

Preparing the File Buffer for the Find

The first step in performing a find is to prepare file buffer for the find. This usually consists of clearing the DDO (send Clear) and then seeding the file-buffer with the information needed to find the record.

Very Important: When you are seeding field values for a find, you must seed the file-buffer and not DDO-field buffer. Do not use the set File_Field_Current_Value message to seed data for a find – it will not work. In this example we cleared the customer DDO and moved a unique Customer identifier into the Customer_number field. For more about using File-Buffers and DDO-Field Buffers see Understanding File Buffers and DDO Field Buffers.

Send the Find Message

Once the file buffers are prepared, you find the record using the Find message. This message requires that you pass a find mode (LT, LE, EQ, GE, GT, NEXT_RECORD, FIRST_RECORD, or LAST_RECORD) and a find index number. In the above example we are performing a "find equal" using Index 1, which presumably is an index of Customer_numbers (if it is not, this find will not work).

After the find, all DEO objects that are attached to any of DDOs involved in the find will be refreshed. The DDOs do this by sending the message Refresh to all DEOs. This is an automatic process of DDOs and makes it very simple for your DEOs to stay synchronized. This also means that your DEOs will change as you find records. If you do not want this behavior see Finding without Updating the DDO.

Check the Status of the Find

If the find succeeds, the data will be loaded into the DDO, CurrentRowId will contain the record’s ID, HasRecord will be true, and the Found indicator will be set to true. If the find failed, the DDO will not be changed and the found indicator will be set to False.

Finding Loops

If you use the Find message to find multiple records, you will usually use a slightly different method of preparing your file buffers. Normally, you will prepare your file buffer for the first find, and perform a find using the "Find greater than or equal to" mode (GE). Successive finds will perform a "Find Greater than" (GT), using the state of the file buffer from the previous find.

Procedure ListUsers

Handle hoDD

Move oUser_DD to hoDD

Send Clear of hoDD

Send Find of hoDD GE 1

While (Found)

Send ProcessThisUser

Send Find of hoDD GT 1

Loop

End_Procedure

Instead of clearing the DDO and using GE, you can also use FIRST_RECORD (or LAST_RECORD with LT, if you wished to find in reverse order).

Procedure ListUsers

Handle hoDD

Move oUser_DD to hoDD

Send Find of hoDD FIRST_RECORD 1

While (Found)

Send ProcessThisUser

Send Find of hoDD GT 1

Loop

End_Procedure

A word or caution when using the Found indicator. This indicator is global and is easily changed. If you are using this, you should only use it immediately after a find operation. If you have additional commands or messages between the find and the test of the Found indicator, you may be testing on a Found value that was changed by some other process. The following code may not work as expected:

// this demonstrates improper technique

Procedure ListUsers

Handle hoDD

Move oUser_DD to hoDD

Send Find of hoDD FIRST_RECORD 1

While (Found)

Send ProcessThisUser

Send Find of hoDD GT 1

Send LogLineChange // THIS COULD CHANGE FOUND!!!

Loop

End_Procedure

In the situation where Found may be altered by other processes, you should immediately move this value to another variable as follows:

// this demonstrates a proper technique

Procedure ListUsers

Handle hoDD

Boolean bOK

Move oUser_DD to hoDD

Send Find of hoDD FIRST_RECORD 1

Move (Found) to bOK

While (bOK)

Send ProcessThisUser

Send Find of hoDD GT 1

Move (Found) to bOK

Send LogLineChange

Loop

End_Procedure

It is considered good programming practice to always move the Found indicator to a local variable.

You will primarily use finding loops like this in non-DEO batch processes. If you use this with DDOs that have DEOs, the DEOs will get refreshed with every find – probably not something you want.

See Also

Find and Clear Operations in DDOs


Request_Find

Send Request_Find of oCustomer_DD GT Customer.File_Number 1

The Request_Find message is similar to the Find message with the exception that the table to find in is passed as a parameter. This allows a parent table find message to be sent to a child. The DDO will direct the find to the proper DDO for you. Normally, you can just use the Find message and send the message directly to the proper DDO.

The syntax for Request_Find is:

Send Request_Find {of hoDD} mode File Index

The Find message actually sends the Request_Find message. The following two lines of code will do the same thing:

// Find using the Find message

Send Find of oCustomer_DD GT 1

// Find using the Request_Find message

Send Request_Find of oCustomer_DD GT Customer.File_Number 1

Assuming that oCustomer_DD is a parent DDO of oOrderHea_DD, the two lines of code will do the same thing:

// Find using the Find message sent directly to the DDO

Send Find of oCustomer_DD GT 1

// Find using the Request_Find message sent indirectly to the parent DDO

Send Request_Find of oOrderHea_DD GT Customer.File_Number 1

Sending a Request_Find message indirectly has some interesting implications for advanced usage. By augmenting the find procedures in DDOs, you can make two different DDOs respond differently to finding the same record. For example, we could augment the Request_Find message for DDO oOrderHea_DD as follows:

Procedure Request_Find Integer iMode Integer iFile Integer iIndex

If (iFile <> Customer.File_Number) begin

Forward Send Request_Find iMode iFile iIndex

end

End_Procedure

In this example, we have disabled any finding of the Customer table records by DDO oOrderHea_DD. Request_Find messages for the Customer table that are sent to oOrderHea_DD are ignored. All other finds are processed, and a find sent directly to Customer_DD would also be processed.

Finds and DEOs

The above samples with Find and Request_Find shows how to perform finds in batch processes. Find support is built directly into DEOs. These finds are initiated when a user presses a key (F7, F8, F9), clicks on a button or selects a menu option. This sends one of the DEO find messages (Find_First, Find_Next, Find_GE, Find_LE, Find_Previous and Find_Last). These messages all send Request_Find to the DEO passing the appropriate find-mode. The DEO Request_Find sends the private Item_Find message to the DDO passing find-mode and the DEO’s file-number and field- number. The DDO Item_Find message performs the find as follows:

  • It determines what index to use for the find (based of file/field numbers)

  • It places the DEO’s data into the required Table’s file.field buffers.

  • It sends Request_Find to itself

  • This performs an attach by sending Attach_Main_File to all effected DDOs.

  • Performs the Find

  • Updates all DDOs by sending OnNewCurrentRecord

  • Updates all attached DEOs by sending Refresh

Below is an example of a simplified DEO Request_Save procedure and an even more simplified DDO Item_Find procedure.

// The DEO Find Process

Procedure Request_Find integer eFindMode

Integer iFile iField

Handle hoDDO

Get Data_File to iFile // table's file number bound to DEO

Get Data_Field to iField // field number bound to DEO

Get Server to hoDDO // DDO serving the DEO

If (iFile>0) begin

send Item_Find of hoDDO eFindMode iFile iField

End

End_procedure

:

:

// the DDO Item_Find process

Procedure Item_Find integer eFindMode integer iFile integer iField

integer iIndex

Get File_Field_Index iFile iField to iIndex // get index of file.field

If (iIndex=-1) Procedure_Return // only find if we have an index

Send Request_Entry_Update iFile 1 // Move DEO data into file-buffers

Send Request_Find eFindMode iFile iIndex // perform the find

End_Procedure

The above code is quite simplified. The actual procedures pass additional parameters and perform additional tasks, which have been removed for the sake of simplicity. This is provided to give you a idea of what the DEO -> DDO find process looks like.

See Also

Find and Clear Operations in DDOs


FindByRowId

Send FindByRowId of hoDDO Customer.File_Number riRecId

The FindByRowId message is used to find a record by its RowId. This is almost always used to re-find a record that was found previously using some other technique. Often you will find a record, store its RowId in a RowId variable or property and then later re-find the record using its RowId.

The syntax for FindByRowId is:

Send FindByRowId {of hoDD} File RowIdValue

After the message, the Found indicator will contain the results of the find.

See Also

Find and Clear Operations in DDOs


Find_By_Recnum

Send Find_By_Recnum of hoDDO Customer.File_Number iRecnum

If your table supports Recnum, you can use this message to find a record by its recnum. This message have been replaced with FindByRowId, which is more flexible and serves the same purpose.

The syntax for Find_By_Recnum is:

Send Find_By_Recnum {of hoDD} File RecnumValue

After the message, the Found indicator will contain the results of the find.

See Also

Find and Clear Operations in DDOs


Request_Assign

Send Request_Assign of oCustomer_DD

If you find a record manually (with the Find command) and want to have the DDO “latch onto” and display the current record, you can send the Request_Assign message. Request_Assign will cause the DDO and its parent DDOs to accept the record(s) in the buffer as their current record and refresh all affected DEOs.

Request_Assign should be used with caution. The DDO does not check to see if the records it is accepting are truly related. Because this is a manual process, it makes it your responsibility to make sure that the records in the buffer are reasonable. Before sending Request_Assign, you should make sure that all records in the buffers are proper.

Clear Customer

Move iNum to Customer.Customer_Number

Find EQ Customer by 1

Relate Customer

Send Request_Assign of oCustomer_DD

Often you can use Request_Assign and FindByRowId interchangeably. The following two samples would yield the same result.

Procedure RefindMyFile RowId riRec

Boolean bOk

Move (FindByRowId(MyFile.File_Number,riRec)) to bOk

Relate MyFile

Send Request_Assign of oMyFile_DD

End_Procedure

or

Procedure RefindMyFile RowId riRec

Send FindByRowId of oMyFile_DD MyFile.File_Number riRec

End_Procedure

See Also

Find and Clear Operations in DDOs


Finding without Updating the DDO

DDOs support several find messages that allow you to find a record without disturbing the state of current DDO. This allows you to take advantage of a DDO’s current constraints without having to actually update your DDO and its attached DEOs. These messages are useful if you need to check the status of some other record, but you do not wish to disturb the contents of the current record in a DDO.

These messages are referred to a “Read” messages to distinguish them from the more normal “Find” messages which do update the DDOs. The “Read” messages are listed below with their corresponding “Find” messages. The parameters used by the “Read” and “Find” messages are the same:

Message is | A non DDO Updating version of
Request_Read | Request_Find
ReadByRowId | FindByRowId
Read_By_Recnum | Find_By_Recnum

Because the Read messages to do not update the DDO, you should always use the File-Buffers to evaluate their values.

// count number of users without changing the current User in the DDO

Function CountUsers returns integer

Handle hoDD

Boolean bOK

Integer iCount

Move oUser_DD to hoDD

Send Request_Read of hoDD FIRST_RECORD Users.File_Number 1

Move (found) to bOK

While (bOK)

Increment iCount

Send Request_Read of hoDD GT Users.File_Number 1

Move (found) to bOK

Loop

Function_Return iCount

End_function

See Also

Find and Clear Operations in DDOs


Reentrancy of DDO Methods

Many DDO methods are not re-entrant. Once a major Data-Dictionary operation (save, find, delete, clear) has begun you cannot start another major operation. The reentrancy restriction applies within DDO structures and across DDO structures.

The messages that are not reentrant are:

  • Clear

  • Clear_All

  • Request_Save

  • Request_Delete

  • Request_Assign

  • Find

  • Request_Find

  • Request_Superfind

  • FindByRowId

  • Find_By_Recnum

For example, you cannot send the Find message in the middle of a Request_Save process (even if the message is being sent to another DDO).

When these types messages are sent in a reentrant state, the normal behavior of the DDO is to ignore them. This can create a problem. If you are sending these messages, you are probably doing so in error. Rather than performing whatever action you are expecting, the message is being silently ignored. As an aid the development, the Data Dictionary Inspector provides a way for reentrancy conditions to be reported as errors during testing.


Global Variables Used by Data Dictionaries

Several global variables are used with Data Dictionaries.

Err Indicator

The save (Request_Save) and delete (Request_Delete) methods are procedures and therefore do not return a value to indicate if the operation succeeded. Instead, the Err global indicator is set and indicates if these operations succeeded. If Err is False, the operation was a success.

Since the Err indicator is global and easily changed you should always move the results of this indicator to a local variable or property.

Boolean bError

:

Send Request_Save of hoDDO // will set Err

Move (Err) to bError

Property Boolean pbDeleteError

:

Send Request_Delete of hoDDO // will set Err

Set pbDeleteError to (Err)

Found Indicator

The various find methods are procedures and do not return a value to indicate if the find was successful. Instead, the Found global indicator is set and indicates if the find succeeded. If Find is True, the find succeeded.

Since the Found indicator is global and easily changed you should always move the results of this indicator to a local variable or property.

Boolean bFound

:

Send Find of hoDDO EQ 1 // will set Found

Move (Found) to bFound

Operation_Mode

Two global integers are maintained during Data Dictionary operations: Operation_Origin and Operation_Mode. These values are maintained by the system and should not be changed. They can be queried and may provide valuable information about a database operation.

When a major Data Dictionary operation begins, the DDO will check Operation_Mode to make sure that there is no other data-server operation currently in process. Since DDO operations are not reentrant, the value of Operation_Mode must be Mode_Waiting. Operation_Mode is set based on the type of operation (clearing, finding, saving, deleting). When the operation is complete, Operation_Mode is again restored to Mode_Waiting. The clear and clear-all operations set it to Mode_Clearing. The find and superfind operations set it to Mode_Finding. The save operation sets it to Mode_Saving, and the delete operation sets it to Mode_Deleting.

Operation_Mode is maintained by the system. It can be queried but it should not be changed.

In advanced applications, you can use the value of Operation_Mode to perform custom augmentations. For example, you may discover that you need two backout behaviors, one for saving and one for deleting:

Procedure Backout

If (Operation_Mode = Mode_Deleting) Begin

: // custom backout for deleting

End

Else Begin // if not deleting, it must be saving

: // custom backout for saving

End

End_Procedure

Operation_Origin

The Operation_Origin indicates which DDO initiated the Data Dictionary operation. It contains the object ID of the originating DDO. The values of Operation_Mode and Operation_Origin are always set at the same time. Operation_Origin is 0 when no data-server operation is in progress.

Operation_Origin was created for the sole purpose of giving the developer more information about a data server operation.

Operation_Origin is maintained by the system. It can be queried but it should not be changed.

In this example, we will use Operation_Origin to make sure that a new record is never saved when that new record is being used as a parent (i.e., the record is not the Main_File of the DDO that received the Request_Save message):

Function Validate_Save Returns Integer

Integer iError

Boolean bHasRecord

Get HasRecord to bHasRecord

If (Not(bHasRecord) AND Operation_Origin<>Self) Begin

Error DFERR_OPERATOR "Can't save this parent record at this time."

End

// we only get here if everything is still ok

Forward Get Validate_Save to iError

Function_Return iError

End_Function

Constrain_Tests_Count and Constrain_Found_Count

Two global integer variables, Constrain_Tests_Count and Constrain_Found_Count, can be used to test if constraints are optimized. Constrain_Tests_Count is incremented after every find and Constrain_Found_Count is incremented if the record is valid (i.e., it meets the constraint requirements). By comparing these two values, you can see if your finds are optimized. The can be tested using the Data Dictionary Inspector.


Changing Field Properties within a DD Object

Field options can be set at the Data Dictionary Object level. In most cases, changing field options at the object level makes little sense and would not be used. There are some options such as No-Enter, Retain and Retain-All where it is quite useful. In particular, the retain options should almost always be set at the object level.

This section will show the various ways field options can be set at the DD object level. Throughout this section we will use as our sample the retain option. Retain is a good sample choice because it is one of the options that is most likely to only be set within the DDO.

The following messages have been created to make it easy to change DDO Field Options.

Set File_Field_Option Set Field_Option

Set File_Field_Option_Clear Set Field_Option_Clear

Set File_Field_Option_toggle Set Field_Option_Toggle

For example, if you had a customer DDO where you wanted to place a retain on City, State and Zip you could do the following:

Object oCustomer_DD is a Customer_DataDictionary

Set Field_Option Field Customer.City to DD_RETAIN

Set Field_Option Field Customer.State to DD_RETAIN

Set Field_Option Field Customer.Zip to DD_RETAIN

End_Object

Masks can be changed within DDOs by setting File_Field_Mask or Field_Mask.

Labels can be changed within DDOs by setting File_Field_Label or Field_label. Labels only are changed if the DEO’s Auto_Label_State is true (usually it is not).

Changing Field Option Settings Dynamically

DDO Field Option settings, Mask Setting and Label Settings may be changed dynamically within an object. If one of these properties is changed in a DDO, all DEOs that use that DD will reflect the change. This can be used to dynamically change retains, change masks, change zero suppress in DDOs, make a field no-entry, etc.

For example, the above Customer DDO could toggle the Retain states for City, State and Zip by pressing Ctrl+F4. Adding the following code to your view could do this.

On_key key_Ctrl+Key_F4 Send ToggleRetain

:

Procedure ToggleRetain

Handle hoDDO

Move oCustomer_DD to hoDDO

Set Field_Option_Toggle of hoDDO Field Customer.City to DD_RETAIN

Set Field_Option_Toggle of hoDDO Field Customer.State to DD_RETAIN

Set Field_Option_Toggle of hoDDO Field Customer.Zip to DD_RETAIN

End_Procedure

Or the following code added to a DEO for Customer.City that would allow you to toggle retains just for this one DEO.

On_key key_Ctrl+Key_F4 Send ToggleRetain

Procedure ToggleRetain

Handle hoDDO

Get Server to hoDDO

Set File_Field_Option_toggle of hoDDO ;

File_Field Customer.City to DD_RETAIN

End_Procedure

A more generic version of this which could be applied to a DEO would be:

Procedure ToggleRetain

Integer iFile iField

Handle hoDDO

Get Server to hoDDO

Get Data_File to iFile

Get Data Field to iField

If (iFile AND hoDDO) begin

Set File_Field_Option_toggle of hoDDO iFile iField to DD_RETAIN

End

End_Procedure

An even more generic version of this is provided below. The following sample code could be added to your view object and would let the user toggle the retain state of any field in the view.

// all objects in this view will send ToggleRetain when

// Ctrl/F4 is pressed.

On_key key_ctrl+key_f4 send ToggleRetain

// When sent from a Ctrl/F4, the message will delegate here to

// the view because ToggleRetain is not directly understood by the

// other objects

Procedure ToggleRetain

Handle hoDEO hoServer

Integer iFile iField

// we want to react to the focus object. The object where

// ctrl/f4 was pressed

Get Focus to hoDEO

// non deos will understand this message via delegation...see below

Get Data_File of hoDEO to iFile

If (iFile=0) Procedure_Return // probably not a DEO that has the focus

Get Server of hoDEO to hoServer

If (hoServer) Begin

Get Data_Field of hoDEO to iField

set File_Field_Option_Toggle of hoServer iFile iField to DD_Retain

End

End_Procedure

// this augmentation was created to allow non-deos to respond to ctrl/f4

// without an error message. Non-deos will delegate this message to

// here, return 0 and do nothing... look at toggle retain

Function Data_File DESKTOP returns integer

Function_Return 0

End_Function

Retains and Setting Default Field Values

Database Builder allows you to create default values for fields. For example, the default value for Customer.state is “FL”. These defaults are applied after field values are retained and consequently they will overwrite the value of the retained data. So, normally you would not use retains and defaults on the same field.

You could use both retains and defaults together to create intelligent defaults. Remember that default values are set within the procedure Field_Defaults. You can augment the Field_Defaults method to create custom default values. During that process you can check to see if a value has been retained and respond however you want.

The following code added to the customer DDO would retain the current state value if it exists. If the state is blank or if the state is “CA”, the default “FL” will get used.

Object oCustomer_DD is a Customer_DataDictionary

Set Field_Option Field Customer.State to DD_Retain

Procedure Field_Defaults

String sState

Forward Send Field_Defaults

// get current state value, could be cleared or a retained value

Get Field_Current_Value Field Customer.state to sState

// If state is cleared or it is CA, set a default of FL

If (sState="" or sState="CA") Begin

Set Field_Changed_Value Field Customer.state to "FL"

End

End_Procedure

End_Object

The Field_Defaults method should only be used to set default values for fields from its own DDO. It should not try to change the value of parent or child DDOs and it should not try to perform additional DDO operations (like an auto-find).

Retains with Parent Fields

We expect you to assign Retains at the object level. Therefore it is up to you to decide if it makes sense to retain fields from parent DDOs. Normally a parent DDO requires an auto-find (see Key Foreign Fields). Therefore retaining any field other than the key-field auto-find field probably makes little sense. Retaining the key field(s) of a parent table may make a great deal of sense.

If you retain a key parent field, the data will be retained but the record will not be found automatically. This is intentional and it gives you two choices. You can either let the auto-find occur when you navigate out of the field or you can force an auto-find as part of the clear operation. We will show you examples of both.

The OrderHea DDO in an order-entry view could define a parent retain as follows:

Object oCustomer_DD is a Customer_DataDictionary

End_Object

Object oOrderHea_DD is a OrderHea_DataDictionary

Set DDO_Server to oCustomer_DD

Set File_Field_Option File_Field Customer.Customer_number to DD_Retain

End_Object

When you clear the order view, the customer ID will be retained but the rest of the customer data will be blank. When you navigate out of the customer-number DEO, an auto-find will occur. This is one way to use retains with parents. It allows the user to select a different customer id before performing the auto-find.

Alternately, your OrderHea DDO could look like this:

Object oCustomer_DD is a Customer_DataDictionary

End_Object

Object oOrderHea_DD is a OrderHea_DataDictionary

Set DDO_Server to oCustomer_DD

Set File_Field_Option File_Field Customer.Customer_number to DD_Retain

// Augment Clear so that it performs an auto-find for the Customer.

// The retained data will be used to perform this auto-find

Procedure Clear

Forward send clear

Send File_Field_Default_Autofind File_Field Customer.Customer_number

End_Procedure

End_Object

Now when you clear the order view, you will see that the entire customer appears to be retained. Actually the key field is retained and then after the clear, an auto-find is performed. The DDO message File_Field_Default_Autofind is used here to perform the auto-find in “default” mode (the DDO’s changed state is not set by the find). Note that you should not try doing an auto-find as part of the Field_Defaults process. The Field_Defaults method can only be used to set defaults for the fields that belong to its DDO and you should never perform database activity (and particularly DDO database activity) within this method.


Understanding the File_Field and Field Keywords

In DataFlex, a file-number identifies tables and a Field number identifies a table-column. Data Dictionaries use the same system: A Data Dictionary’s Table is identified by a File Number (Main_File), and each of its fields is identified by a Field Number.

This has the advantage the tables and fields can be represented in an abstract manner. The entire DataDictionary class is created using this abstraction. This allows you to create classes (perhaps a custom sub-class of the DataDictionary class) that also allows for this abstraction. For example, you could create a custom method that returns a field’s long label with all spaces removed. In addition, if the data type of the field is numeric it will place a “$” at the end of the label:

Class cMyDataDictionary is a DataDictionary

Function Field_NoSpaceLabel Integer iField returns string

String sLabel

Integer iFile iFieldType

Get Main_File to iFile

Get_Attribute DF_FIELD_TYPE of iFile iField to iFieldType

Get Field_Label iField DD_LABEL_LONG to sLabel

Move (Replaces(" ", sLabel, "")) to sLabel

If (iFieldType = DF_BCD) begin

Move (sLabel + "$") to sLabel

End

Function_Return sLabel

End_Function

End_Class

This class will now work properly with any Data Dictionary using any table and any field.

While the use of file and field numbers is very useful for creating abstracted processing it becomes more cumbersome when you wish to use these interfaces in a non-abstract manner (i.e., using them when you know the names of the table and fields but you don’t know their numbers). You don’t want to have to keep track of numbers; you want to use meaningful names.

Symbols, commands and special command syntax are provided to make this easy.

See Also

The TableName.File_Number Symbol

The Field Keyword

The File_Field Keyword

File versus File_Field


The TableName.File_Number Symbol

A table’s file number is represented by the symbol TableName.File_Number. When a Data Dictionary class is created its Main_File property is set as follows:

Set Main_File to Customer.File_Number

The Get_FieldNumber Compiler Command

While there is no direct symbol to identify a field, there are commands and keywords to make this process simpler. A compiler command, Get_FieldNumber, can be used to extract a field number from a file.field. It has the following format:

Get_FieldNumber file.field to field_num

It could be used in the following way:

Get_FieldNumber Customer.Name to iField

Set Field_Options iField to DD_AutoFind

Our custom Field_NoSpaceLabel could be accessed as follows:

Get_FieldNumber Customer.Name to iField

Get Field_NoSpaceLabel iField to sLabel

See Also

Understanding the File_Field and Field Keywords


The Field Keyword

Because this type of access will be required so frequently in Data Dictionaries, a special command parameter has been provided to support this within a single message. When the keyword Field is used, the file.field name following it will be converted to a field number.

Set Field_Options Field Customer.Name to DD_AutoFind

Get Field_NoSpaceLabel Field Customer.Name to sLabel

Most of the Data Dictionary methods that require that a field number be passed are prefixed with “Field_” (e.g. Set Field_Options, Get Field_Current_Value). When you see a method that starts with “Field_” you know that you need to either pass a field number or use the Field keyword syntax.

Note: The use of the Field keyword will tell the compiler that the next symbol (in this case, Customer.Name) should not be treated as a file.field variable (which would be a data value) but should represent a field number. If the Field keyword were removed, you’d get a table value and not a field number.

See Also

Understanding the File_Field and Field Keywords


The File_Field Keyword

Some Data Dictionary methods require that both a file number and a field number be passed. These messages are called File_Field messages and are always prefixed with the keyword “File_Field_” (e.g. Get File_Field_Current_Value).

Move Customer.File_Number to iFile

Get_FieldNumber Customer.Name to iField

Set File_Field_Current_Value iFile iField to "John"

The keyword, File_Field, allows you to shorten this as follows:

Set File_Field_Current_Value File_Field Customer.Name to "John"

See Also

Understanding the File_Field and Field Keywords


File versus File_Field

Most DDO field properties have two types of messages, a Field property and a File_Field property. For example, you could set a DDO’s Field value with one of these two lines:

Set File_Field_Current_Value of hoDD File_Field Customer.Name to sName

or

Set Field_Current_Value of hoDD Field Customer.Name to sName

These messages perform the same function. Their usage depends on which DDO is receiving the message.

When you use the Field variant of this message the DDO that receives the message must be the DDO that owns the file-number passed after the field parameter. In the above example, the DDO receiving the message must be the Customer DDO. If it is not, no error will result, but the wrong field will be updated. The Field keyword converts its following File.field parameter to a single field value – the file number is assumed to be the table of the DDO receiving the message.

When you use the File_Field variant of this message, the DDO that handles this message is passed both a file number and a field number. If the file number is not the same as the DDO’s main file, the DDO will search the DDO structure for a DDO that owns this table and redirect the message to that DDO. In the above example, you could send the message to the OrderHea DDO and that DDO would properly redirect the message to the Customer DDO. The File_Field keyword converts its following File.field parameter into two parameters: a file number and a field number.

Why use the Field method? Use the Field keyword syntax if you are sure you are sending the message to the correct DDO and you want to type a little less code. Also, this method is slightly more efficient than the File_Field method, although the difference in efficiency is probably imperceptible. When writing code within a Data Dictionary class or object you can usually use the Field keyword method. Note that all of the properties within a Data Dictionary class use this syntax.

Why use the File_Field method? Use the File_Field keyword syntax if you might be sending the DDO message to a DDO that does not own the field. Also, you can use this method if you simply don’t want to worry about the difference between File and File_Field and you would prefer to use a single syntax that works in all situations. You are more likely to use the File_field keyword when you are programming outside of a Data Dictionary class or object.

Whichever method you use, you must make sure that you always only use the Field keyword with Field properties and that you only use the File_Field keyword with File_field properties. If you mix these, errors will occur which will be hard to trace. Do not do the following:

// this uses a Field parameter with a File_field message. Wrong!

Set File_Field_Current_Value of hoDD Field Customer.Name to sName

// this uses a File_Field parameter with a Field message. Wrong!

Set Field_Current_Value of hoDD File_Field Customer.Name to sName

See Also

Understanding the File_Field and Field Keywords


File versus File_Field

Augmenting the File_Field and File Messages

If you need to augment one of these methods, you should always augment the Field_ version of the message in the appropriate owner class or object. Never augment the File_Field_ version of a message.

Function Field_Current_Value integer iField Returns String

String sValue

Forward Get Field_Current_Value iField to sValue

:

: // do some custom augmentation

:

Function_Return sValue

End_Function

Note that when you forward the message, you just forward the parameter and you do not use the FIELD keyword.

See Also

Understanding the File_Field and Field Keywords


Understanding File Buffers and DDO Field Buffers

Before you can access information from a table it must be opened. When a table is opened a file buffer is created. Data in this buffer is accessed using the File.Field variables.

Open Customer

:

Move Customer.Name to sName

Move sName to Customer.Name

A file buffer is global and only one instance of a table can be opened at one time. This means that there is only one instance of any File.Field variable.

When a Data Dictionary object is created, the table used by that DDO is opened which creates access to the same global file buffer. Since you are allowed to have multiple object instances of the same Data Dictionary in an application, DDOs must provide a method to access data this is just local to itself. It does this by creating local DDO-Field buffers. These field buffers allow you to work around the restriction of a global file buffers. DDO can contain unique field data, even if the refer to the same table.

Get File_Field_Current_Value of hoDD File_Field Customer.Name to sName

Set File_Field_Changed_Value of hoDD File_Field Customer.Name to sName

Get Field_Current_Value of hoDD Field Customer.Name to sName

Set Field_Changed_Value of hoDD Field Customer.Name to sName

At a low level, Data Dictionaries are restricted to using global file buffers. They are specifically designed to deal with this (see Synchronization of File Buffers and DDO-Field-Buffer). As long as you use DDO-Field buffers, the complexity of dealing with global file buffers is hidden from you.

When working with Data Dictionaries you can access table column data by referring to the Table’s File-Buffer or by referring to the Data Dictionary object’s DDO-Field-Buffer. As a developer, you need to know when you can and should use each type of buffer.

In some cases it does not matter which method you use, and you can use whatever is simplest (usually File-Buffer), while at other times it is critical that you use the right method. Here are some guidelines to assist you in choosing the proper coding style.

See Also

When to Use the DDO-Field-Buffer

When to Use the File-Buffer

Synchronization of File Buffers and DDO-Field-Buffer


When to Use the DDO-Field-Buffer

Get Field_Current_Value of hoDD Field Customer.Name to sName

Set Field_Changed_Value of hoDD Field Customer.Name to sName

You should use the DDO-Field-Buffer value under the following conditions:

  1. Any time you are changing a field value in anticipation of saving this change, you must use the DDO-Field-Buffer value. Normally you will use Set Field_Changed_Value to do this. If you move your changes directly to the File Buffer, they will be overwritten by the values in the DDO-Field-Buffer.

Function NewOrder Integer iCustNo String sTerms ;

String sShipVia Returns Boolean

Boolean bErr

Handle hoOrderDD hoCustDD

Move OrderHea_DD to hoOrderDD

Move Customer_DD to hoCustDD

Send Clear of hoOrderDD

Move iCustNo to Customer.Customer_Number

Send Find of hoCustDD EQ 1

Set Field_Changed_Value of hoOrderDD Field OrderHea.Terms to sTerms

Set Field_Changed_Value of hoOrderDD Field Orderhea.Ship to sShip

Get Request_Validate of hoOrderDD to bErr

If not bErr Begin

Send Request_Save of hoOrderDD

Move (Err) to bErr

End

Function_Return bErr

End_Function

  2. If you create custom field validation, exit or entry methods in your Data Dictionary class or your object you should use the DDO-Field-Buffer (File_Field_Current_Value) to retrieve a field’s current value. You can use the File-Buffer to return the field’s original, unchanged value. Note that the validation field’s current value is passed in to field validation functions. You would only need to retrieve some DDO Field value other than the field you are currently validating.

// Method in Data Dictionary Class

Set Field_Entry_msg Field Orderdtl.Price To Entering_Price

:

Procedure Entering_Price Integer iField Number nAmnt

If (nAmnt=0) Begin

Get File_Field_Current_Value File_Field Invt.Unit_Price To nAmnt

Set Field_Changed_Value iField To nAmnt

End

End_Procedure

  3. If you need to retrieve a DDO’s current field value you must use the DDO-Field-Buffer (File_Field_Current_Value).

// Method in Data Entry Object

Procedure RefreshTotal

Integer iQty

Number nAmnt

Handle hoOrderDtl

Move oOrderDtl_DD to hoOrderDtl

Get Field_Current_Value of hoOrderDtl Field Orderdtl.Qty To iQty

Get Field_Current_Value of hoOrderDtl Field Orderdtl.Price To nAmnt

Set Value to (nAmnt * iQty)

End_Procedure

Note

If you need to retrieve the original, unchanged value of the field, you must use the File Buffer. If the field value is unchanged (as is always the case in reports) you can use either the File Buffer or the DDO Field buffer. This is discussed in the next section.

See Also

Understanding File Buffers and DDO Field Buffers


When to Use the File-Buffer

Move sName to Customer.Name

Move Customer.Name to sName

Send WriteHtml (HtmlEncode(Customer.Name))

There are some conditions where you should always move data directly to or from the file-buffer, bypassing the DDO’s field buffer. Those are:

  1. When manipulating data within DDO events. During DDO operations (saves, finds, deletes, clears) various events are triggered. When these events are called the Data Dictionary has already placed the correct data in the file-buffer. If you create event augmentations you must always refer to the data in the file-buffers. In some cases, the data in the global file-buffer will not be the current field value – but it will be the right value for the event. For example, the Backout event must be called with the original data values and not with changes you might have made in your DDO. The DDO events which require that you use file-buffer data are:

  • Update

  • Backout

  • Creating

  • Deleting

  • Validate_Save

  • Validate_Delete

  • Attach_Main_File

  • Save_Main_File

  • Clear_Main_File

  • Relate_Main_File

  • OnNewCurrentRecord

  • OnConstrain

While it is possible to augment events within an object, most often these events are created within the Data Dictionary class (with OnConstrain being a notable exception).

While this may seem counter-intuitive (to use direct file buffers during events), it is very important that you follow this advice. During a DDO operation, events are called during various stages of a file buffer’s state. For example, backout is called before the local DDO-Field-Buffer data has been updated to the file-buffer, while update is called after the DDO-Field-Buffer data has been moved to the file-buffer. If you use the file-buffer, you will always be changing the right data at the right time.

Procedure Creating

String sCustName

Integer iSpacePos

Forward Send Creating

SysDate Customer.Created

Move Customer.Name to sCustName

Move (Trim(Uppercase(sCustName))) to sCustName

Move (Replaces(' ',sCustName,'')) to sCustName

Move sCustName to Customer.Login_Name

End_Procedure // Creating

  2. You should always move data directly to the file-buffer when you are performing finds (Find, Request_Find, etc.). This almost always causes confusion with new developers. When performing find operations, seeding the local DDO-Field-Buffers will have no effect; you must seed the global file-buffers. This allows you to perform a find without changing the current data in your DDO. This is required when you use finding methods that are not supposed to update your DDO.

// This function is used to find a customer by Customer number

// and return the RowId for that customer

Function SearchUser Integer iCustNo Returns RowId

RowId riRecId

Handle hoDD

Move oCustomer_DD to hoDD

Send Clear of hoDD

Move sCustNo to Customer.Customer_Number

Send Find of hoDD EQ 1

Get CurrentRowId of hoDD to riRecId

Function_Return riRecId

End_Function // SearchUser

When seeding data for a find, always use the File-Buffer. When changing data for a save, always use the DDO-Field-Buffer.

  3. If you need to retrieve the value of a DDO field, you can always use the Field_Current_Value message. This retrieves the current value of a field from the DDO’s local DDO-Field-Buffer. Note that this is the current value – if a field value was changed since it was last found, those changes will be retrieved. The file-buffer will contain the old value of the field – the field value before it was changed. If a field is unchanged, the DDO field value and file buffer value will be the same. Therefore, if you know that a field is unchanged you can use the file buffer to retrieve field values. If a field has not been changed, the following two code examples would return the same result.

Get Field_Current_Value of oCustomer_DD Field Customer.Name to sName

and

Move Customer.Name to sName

Because it is syntactically simpler to directly access the file-buffer, we will often use this method. Reports, which need to return unchanged field values, will use the simpler file buffer syntax.

Procedure Body

string sFont

Integer iFontSize iStyle

Send Update_Status (String(Customer.Customer_number))

Move "arial" to sFont

Move 8 to iFontSize

Move (font_default) to iStyle

DfWritePos Customer.Customer_Number 0.8 (iStyle + Font_Right) 0

DfWritePos Customer.Name 2 iStyle -1 4.98

DfWritePos Customer.Address 7 iStyle -1 4.98

DfWritePos Customer.City 12 iStyle -1 3.98

DfWritePos Customer.State 15 iStyle -1 0.98

DfWritePos Customer.Zip 17 iStyle -1 1.98

End_Procedure // Body

Immediately after a successful find, the DDO-Field-Buffer and the File-Buffer will contain the same values. After additional processing, this may no longer be case, even if the DDO-Field-Buffer has not changed. This is discussed in Synchronization of File Buffers and DDO-Field-Buffer.

See Also

Understanding File Buffers and DDO Field Buffers


Synchronization of File Buffers and DDO-Field-Buffer

When running applications with data-entry views, it is expected that your applications will contain multiple views and that some of these views will need to use the same table. Each view will have its own DDO structure encapsulated inside it. Very often these DDO structures use the same table, the same Data Dictionary subclass, and will probably even have the same object name. Further, all of these objects will share the same, single file-buffer. As long as you don’t try to put two DDOs based on the table in the same view, you will not have any problems.

The critical database operations are save and delete. These operations are safe. A DDO will make sure that it has the correct data in its file-buffer before it proceeds. This is all automatic. This means that the buffers are correct when the Data Dictionary events are processed (update, backout, deleting, Validate_Save, Validate_Delete, etc.). The DDOs notify DEOs that they need to display data by sending the DEOs the refresh message. The table’s file-buffers are correct during the refresh process.

There are times when the file buffers will not contain the same record as your DDO. During a multi-view, data entry application, it is possible that the file-buffer will not contain the data you think it does. If a user switched views in the middle of a process, it is possible that the record that the DDO thinks it is working with (which is identified in the DDO’s CurrentRowId property and the DDO-Field-Buffer) is not the record that is in the file-buffer. This doesn’t bother the DDO, but it may get you in trouble if you have DEO code that is based on the contents of a file-buffer.

The best way to deal with this is to always use the DDO-Field-Buffer value (Field_Current_Value).

Refind_Records

If you want to make sure your File-Buffer is synchronized with the DDO-Field-Buffer you can send the message Refind_Records. This checks to see if the record in the File-Buffer is the same as the record in the DDO-Field-Buffer. If it is not, it will re-find the record. This updates the File-Buffer ensuring that the File-Buffer and DDO-Field-Buffer are synchronized. This performs this check for the DDO’s Table and all ancestor (parent, grandparent, etc.) DDOs.

The standard method of sending this message is:

Send Refind_Records {of DDO-Object}

For example, you may have a report that runs a batch process in the report’s body section which may switch records (the batch process may contain a DDO structure based on the same table). By sending Refind_Records after the process, you can be sure your File-Buffer contains the data for the record in your DDO.

Procedure Body

string sFont

integer iFontSize iStyle

Handle hoDD

Get Server to hoDD // the DDO currently handling the report

Send Update_Status (String(Customer.Customer_number))

// this runs a batch process which may find a different customer

Send CheckForDuplicateCustomers Customer.Customer_number

// this makes sure that the customer data is correct.

Send Refind_Records of hoDD

Move "arial" to sFont

Move 8 to iFontSize

Move (font_default) to iStyle

DfWritePos Customer.Customer_Number 0.8 (iStyle + Font_Right) 0

DfWritePos Customer.Name 2 iStyle -1 4.98

DfWritePos Customer.Address 7 iStyle -1 4.98

DfWritePos Customer.City 12 iStyle -1 3.98

DfWritePos Customer.State 15 iStyle -1 0.98

DfWritePos Customer.Zip 17 iStyle -1 1.98

End_Procedure // Body

This example is somewhat contrived - you should rarely need to use Refind_Records.

See Also

Understanding File Buffers and DDO Field Buffers


Using Validation Tables

If a field needs to be validated as either of two values, a limited set of values, or a range of values, you can use one of the extended validation methods already detailed (checkbox, range, and check). In many cases you will find that these types of validations are too limited for your needs. In such a case you may use a Validation Table to handle field validations. Using a validation table has the following advantages:

  • You can specify a larger number of valid values than can easily fit within a check string

  • You can specify and display a description for each value in your list

  • You can dynamically maintain your list of values (i.e., the values do not have to be coded in your program)

  • You can choose to make selection from this list optional (i.e., you can display a list of suggested values during data entry, but you are not required to make a selection from the list).

The concept of a validation table is simple.

  • In your program you must create a validation table object.

  • This object is then assigned to a field(s) in a Data Dictionary by using the Field_Value_Table message.

  • Once assigned, the field will use this object as a validation and display resource.

While the concept and usage of validation tables is simple, their benefits are considerable. Because special objects handle the validation and display, it is possible to handle just about any type of list-validation condition. In fact, you could create your own custom validation-table subclasses. It is simply an object containing a list of valid values and, optionally, descriptions that understands a pre-defined message protocol so that DDOs and DEOs may communicate with it.

While you could create your own custom validation subclasses, you will probably not need to. We have provided a series of validation table classes that will probably handle all your needs.

ValidationTable

Used to provide a static list of data values

DescriptionValidationTable

Used to provide a static list of data and description values

FileValidationTable

Used to acquire validation information from a table of your choosing

CodeValidationTable

Used to acquire validation information from a single, pre-defined Code Validation table

You are most likely to use ValidationTables, DescriptionValidationTables and CodeValidationTables. These tables can be maintained entirely within Database Builder.

The following sections will describe these different types of Validation Tables and explain how you can create your own validation table classes. You can do this by sub-classing one of the existing classes or by building a complete new class.

See Also

External Validation Tables

Creating Your Own Validation Table Classes


Creating a ValidationTable Object

A series of simple examples should provide you with the information required to create your own validation table objects. We will start this example by not using a validation table at all. Assume we have a data-entry object defined in one of the following ways:

Object oStatus_Entry is a dbForm

Entry_Item Customer.Status

End_Object

Or

Object oStatus_Entry is a dbComboForm

Entry_Item Customer.Status

End_Object

We will not change any of these data-entry objects (DEOs). We will change the validation types used by the Field Customer.Status in the data dictionary. We will start by not using a validation table at all. Instead, we will assign Customer.Status the following check string:

Set Field_Value_Check field Customer.Status to "O|C|D"

By assigning this field, we will get two behaviors:

  • Before saving this record, the value of Customer.Status must be O, C, or D.

  • If a user requests a list of valid values, the choices O, C and D will be presented. The DEO class used will determine the type of list that will be presented. If the DEO is a simple entry form, (dbForm), a popup list will appear when a user selects a prompt. If the DEO is a combo form (dbComboForm), the drop-down combo list will contain the valid values. If the DEO is a radio group (dbRadioGroup) with radio forms inside it, the choices will be presented as a radio list. Each of these DEOs knows how to interact with the Data Dictionary in a manner appropriate to its capabilities. This entire process is automatic.

We will now replace the Field_Value_Check message with a Field_Value_Table message.

Set Field_Value_Table field Customer.Status to oStatusTable

This is not enough to run (or even to compile). We must now create an object that can be used by this field. We will do this by creating the global ValidationTable object named oStatusTable as follows:

Object oStatusTable is a ValidationTable

Procedure Fill_List

Send Add_table_value "O"

Send Add_table_value "C"

Send Add_table_value "D"

End_Procedure

End_Object

:

Class Customer_DataDictionary is a DataDictionary

Procedure Define_Fields

:

Set Field_Value_Table field Customer.Status to oStatusTable

:

This provides the exact same functionality as field check validation. Currently, the only advantage of using a validation table is that this object could be used by other Data Dictionary views that also needed access to the same validation types.

In addition, by changing several properties, we can provide some additional validation capabilities. Those properties are:

  • Allow_Blank_State

  • Validate_State

  • List_Title

If Allow_Blank_State is true, an empty value ("") is also considered to be a valid value. This would now allow the field value to be O, C, D or nothing (presumably undefined).

If Validate_State is set to false, validation will not be performed on this field. Why would you want this? There are times when you want to provide the user with a set of suggested but not mandatory choices. By setting Validate_State to false, you still get access to the lists (popup lists, combo lists and radio lists) but the values in the lists are not required. They are suggestions.

Setting a List_Title provides a custom title to be used when a popup prompt list is generated for this list.

The other advantage of using the validation object is that you may fill your list in a variety of ways. For example, you could load your choices for the following object with a serial list of line-delimited options.

Object oStatusTable is a ValidationTable

Set Validate_State to False // Suggested, not required

Set List_Title to "Customer Status" // title for popup prompt list

Procedure Fill_List

String sStat

Direct_Input "Status.txt"

If Not (SeqEof) Begin

Readln sStat // read first line

While not (Seqeof)

Send Add_Table_Value sStat

Readln sStat // read in a status value

Loop

Close_Input

End

End_Procedure

End_Object

See Also

Using Validation Tables

Sequential File Input and Output


Creating a DescriptionValidationTable Object

By basing our validation table on the supplied ValidationTable subclass, DescriptionValidationTable, we can provide a description for each item.

Object oStatusTable is a DescriptionValidationTable

Set List_Title to "Customer Status"

Procedure Fill_List

Send Add_Table_Value "O" "Opened"

Send Add_Table_Value "C" "Closed"

Send Add_Table_Value "D" "Flagged for Deletion"

End_Procedure

End_Object

The validation process and the data that is read and written to your database will remain unchanged. Your DEOs will handle this table a little bit differently. Depending on the class of the DEO, the data will be presented in data format, description format, or as a combination of data and description. Your entry forms (dbForm and dbGrid) will always display the actual data values. A prompt list will present the data and the description when invoked from the DEO. Combo forms (dbComboForm, dbGrids with combos) and radio entry objects (dbRadioGroup) will display just the description. With prompt lists, combos and radios, you can actually choose to display any combination of data and description. However, you should find that the description-only model is the most-widely used method across applications.

See Also

Using Validation Tables


Creating a FileValidationTable Object

The FileValidationTable class, a subclass of DescriptionValidationTable, makes it easy to load your data and descriptions from a table. You do this by setting properties to define the table’s file number; the fields for the data value (the code) and the description, and the index used to load the records.

The following example will create a validation table, which will load its data from a table named CustStat whose first field contains the data value and the second field contains the description. It will load all records using Index 1 of CustStat.

Object oStatusTable is a FileValidationTable

Set Main_File to CustStat.File_Number

Set Code_Field to 1 // code is in field 1

Set Description_Field to 2 // description is in field 2

Set Ordering to 1 // load by index 1

End_Object

Actually, the default values for Code_Field, Description_Field and ordering default to the above values, so this object could actually be defined as follows:

Object oStatusTable is a FileValidationTable

Set Main_File to CustStat.File_Number

End_Object

A validation table is a batch-based table. All items are loaded at one time. Therefore, it is expected that the list of items in any validation table will be (reasonably) short and will be static (once the table is loaded, the contents of the list will not change) throughout any session in which it is used. If you have many items or your item list is dynamic, you need an actual relationship between these tables and to use relational validation and lookup techniques.

If your list of valid items remains small but may change during usage sessions, you can create a non-static validation table by setting the Static_State property to false. When you do this, the table is always refilled before it is used for validation and the display list(s) (combo list, prompt list) is refilled each time it is displayed. Creating a non-static table will slow down processing because the validation table will be constantly refilling. If the number of items is small enough, this slowdown will not matter.

Additional properties named Type_Field and Type_Value can be used to load a subset of records from a table. When these are used, only records whose value in Field Type_Field matches the value of Type_Value will be loaded. When you constrain the records thus, make sure that your ordering index is optimized to ensure fast loading (index should be made up of Type_Field + Code_Field). You will probably never need to use these properties because the subclass, CodeValidationTable, provides the means and the table to accomplish this directly.

See Also

Using Validation Tables


Creating a CodeValidationTable Object

The CodeValidationTable is the most powerful and easiest to use type of Validation Table. When you use a CodeValidationTable object, your validation values will be loaded from the CodeMast table. This table contains sets (or types) of validation values. By specifying the type, all codes and descriptions for that type are made available, and no others. The data in this table can be maintained by using DataBase Builder or the Code Maintenance program.

Object oStatusTable is a CodeValidationTable

Set Type_Value to "Status"

End_Object

The following code table will define a non-static table for all Status types. The display objects will be titled “Customer Status Types”, and the list will not be used for validation.

Object oStatusTable is a CodeValidationTable

Set Type_Value to "Status"

Set List_Title to "Customer Status Types"

Set Static_State to False

Set Validate_State to False

End_Object

It is important to note that the DEOs require no changes to support any of the above validation tables. These DEOs simply ask for data and request validation services from their Data Dictionary. They don’t “care” how this information is obtained. This makes validation tables extremely flexible and powerful.

See Also

Using Validation Tables


External Validation Tables

In most cases, your custom validation tables will be used by a single Data Dictionary and will reside with the Data Dictionary’s class package. In some cases you may wish to make a validation table object global. This makes the one object available to any Data Dictionary that requires its validation services. In such a case, the same list of valid items and descriptions is available to all data dictionaries. Global validation-table objects must be placed outside the Data Dictionary-subclass definition. You are advised to place the external Validation Table in include it in your DataDictionary with the use command.

// StatusValueTable.pkg

Use DDValTbl.pkg

Object oStatusTable is a CodeValidationTable

Set Type_Value to "Status"

Set List_Title to "Customer Status Types"

End_Object

// End of file

// Customer_DataDictionary.pkg

Use StatusValueTable.pkg // creates object named oStatusTable

Class Customer_DataDictionary is a DataDictionary

Procedure Define_Fields

:

Set Field_Value_Table field Customer.Status to oStatusTable

In the above example, the same physical validation object will be available to all objects.

See Also

Using Validation Tables


Creating Your Own Validation Table Classes

The validation-table classes provided (ValidationTable, DescriptionValidationTable, FileValidationTable, and CodeValidationTable) will probably handle all your validation-table needs. If they do not, you are encouraged create subclasses.

If you wish, you can create complete custom validation classes. You can make this class work any way you want as long as it supports the pre-defined interface. When a validation table is assigned to a field in a Data Dictionary, the Data Dictionary and the DEOs using it expect this validation table to adhere to a specific interface.

A validation table must support the following public interface:

Get Static_State to State

Get Validate_State to State

Get Validate_Value data_value to State

Get Number_Elements to Val

Get List_Title to TitleValue

Send Request_Fill_From_List ObjectId MessageId

As you can see, this interface is quite simple and abstract. The actual methods for processing these requests are internal and may be quite complicated. As long as the interface is adhered to, you can make a validation table act any way you wish.

Static_State Property

This property is used to specify whether display lists (popup prompt lists, combo drop-down lists, and radio lists) should be refilled each time they are required by a DEO. Normally, validation tables should be static (this property should be true).

It will be called as follows:

Get Static_State to bIsStatic

A typical class implementation of this might be:

Property Boolean Static_State True

Validate_State Property

This property specifies whether the list should be used for validation purposes. If true, you will have to define the message get Validate_Value.

It will be called as follows:

Get Validate_State to bPerformValidate

A typical class implementation of this might be:

Property Boolean Validate_State True

Validate_Value Function

If Validate_State is true, the message Validate_Value will get sent to this object. It is passed the data value to be validated. Returning a zero value indicates that the value is valid; a non-zero value indicates that validation failed.

It will be called as follows:

Get Validate_Value sDataValue to bNotValid

The method used to validate this value is left entirely to the class. Here is a sample implementation for this method:

Function Validate_Value string sDataValue returns Boolean

Integer iItems i

String sData

Get Number_Values to iItems // number of items in validation table

For i from 0 to (iItems-1)

get Data_Value i to sData

If (sDataValue=sData) Function_Return False // false = is valid

Loop

Function_Return True // true = is invalid

End_Procedure

This example assumes that your validation-table class defines and understands the messages get number_values and get data_value.

Number_Elements Function

This determines how many elements are used to represent a data item. This is used by visual objects (prompt lists, combo forms, radio forms) to determine how to display data.

Currently the values 1 and 2 are supported.

  • 1 - indicates that only a data (code) value is available

  • 2 - indicates that both data and description values are available.

It will be called as follows:

Get Number_Elements to iNumOfElement

A typical class implementation of this might be:

Property Integer Number_Elements 2

List_Title Property

Objects may request a title for a prompt list.

It will be called as follows:

Get List_Title to sTitle

A typical class implementation of this might be:

Property String List_Title "Terms List"

Request_Fill_From_List Procedure

This message is used when another object (prompt list, combo form, etc.) needs to be filled with valid values. When it is received, the validation object must send a specified message (MessageId) to a specified object (ObjectId), passing information about each item in the validation table.

The following information must be passed back to ObjectId: Item number, Data value, Description value, table number, and RowId. Item number and data value must contain information. If Description value, table number, or RowId do not exist, you may pass empty values ("", 0, and (NullRowId()) ), but five values must be passed back every time.

It will be called as follows:

Send Request_Fill_From_List iObjectId iMessageId

Here is a sample implementation for this method:

Procedure Request_Fill_From_List integer iObjectId integer iMessageId

Integer iItems i

String sDataVal sDescVal

Get Number_Values to iItems // number of items in validation table

For i from 0 to (iItems-1)

get Data_Value i to sDataVal

get Description_Value i to sDescVal

Send iMessageId of iObjectId i sDataVal sDescVal 0 (NullRowid())

Loop

End_Procedure

This example assumes that your validation-table class defines and understands the messages get number_values, get data_value, and get description_value.

Note

RowId is passed for compatibility reasons and is never actually used. You may always pass this as (NullRowId()).

See Also

Using Validation Tables


Masks in Windows Applications

Masks can be used within Visual DataFlex windows application to:

  • Restrict data entry to a field to a defined pattern of numbers, letters, and punctuation marks.

  • Provide displays of field values complying with pre-defined patterns and formatting. For example, it is possible to display date values as abbreviations such as days of the week or months of the year. Translating from numbers to names is sensitive to the Windows locale settings at using sites.

As a convenience, you may assign these masks to fields in your Data Dictionary. When one of these fields is used by a windows control that supports masking (e.g. dbForm, dbList, dbGrid), this mask will be automatically applied to the control.

Field masks are created by assigning a mask-type (Field_Mask_Type) and an optional mask-string (Field_Mask).

See Also

Defining the Mask Type (Field_Mask_Type)

Defining a Mask (Field_Mask)

Sample Mask Strings

Zero suppression with Masks


Defining the Mask Type (Field_Mask_Type)

Setting Field_Mask_Type determines the type of mask for the field. Selecting a mask type causes two masking effects.

  1. It assigns a default mask to the field. There is a default mask for each mask type.

  2. The mask type determines which tokens are available for creating the mask string. These are summarized in Defining a Mask.

Valid settings for Mask Type and the associated default masks are:

Field_Mask_Type Setting | Default Mask | Notes
Mask_Window | None | Use this for setting up masks for ASCII fields. Same as the text mask type.
Mask_Currency_Window | $,*;($,*) | Use this to apply currency formatting to a numeric field.
Mask_Numeric_Window | * | Use this for numeric fields.
Mask_Date_Window | None | Use this for formatting date fields.

See Also

Masks in Windows Applications

Locale Formatting Functions


Defining a Mask (Field_Mask)

If the default mask provided for the Field_Mask_Type does not fit the needs of your field you may customize the mask by explicitly creating you own field mask. You do this by setting the Field_Mask property. The mask rules that can be applied to a field is determined by the Field’s mask type as follows:

Mask Window or None

Token | Meaning
# | Any numeric digit (0-9)
@ | Any alphabetic character (A-Z, a-z)
! | Any punctuation character
* | Any single printable character

Date Mask Type

Token | Meaning
m | Month number, displayed as 1, 2, 3, …, 12
mm | Month number, displayed as 01, 02, 03, …, 12
mmm | Month name abbreviated (translated to local language setting). For example, in English this would display Jan, Feb, Mar, …, Dec
mmmm | Month name (translated to local language setting). For example, in English this would display January, February, March, …, December
d | Day number, displayed as 1, 2, 3, …, 31
dd | Day number, displayed as 01, 02, 03, …, 31
ddd | Day of the week name abbreviated (translated to local language setting). For example, in English this would display Sun, Mon, …, Sat
dddd | Day of the week name (translated to local language setting). For example, in English this would display Sunday, Monday, …, Saturday
yy | Year number, displayed as 00, 01, 02, …, 99
yyyy | Four-digit year number displayed as 2000, 2001, 2002, …, 2099
/ | This represents the local date separator.

Special Note:

  • Tokens for the month and day names are case-sensitive. That is, Mmm would produce Apr, while MMM would produce APR for the fourth month.

  • In most cases, the mask string is not used, but instead the Windows regional settings are used to determine how to display dates.

Numeric and Currency Mask Type

Token | Meaning
, | This token formats the number by inserting a thousands’ separator every three places to the left of the decimal place. The thousands’ separator is translated into the local thousands’ separator symbol.
. | This token represents the position of the decimal point. It is translated into the local decimal point symbol.
; | The semi-colon token is used to split the mask string into two parts. The left half is be used to format numbers greater or equal to 0 and the right half for numbers less than 0.
# | Specifies no digit or one digit.
* | Specifies any number of digits.
0 | Specifies one digit, or a 0 (zero).
$ | Will be replaced with currency symbol from Windows regional settings.

Special Note:

  • Be careful with $, because only the currency symbol changes, not the value. For example, the order entry example in Europe shows the EURO symbol while in the USA it shows the DOLLAR and in England it shows a POUND sign.

Non-token characters in mask strings are treated as literals (they are inserted where seen into masked field values). This includes the space character. Token characters can be deactivated and made to appear as literals in masked values by preceding them with a backslash (\).

See Also

Masks in Windows Applications

Locale Formatting Functions


Sample Mask Strings

Text Mask Type or None

Mask | Raw Data | Formatted Data
###-##-#### | "012345678" | 012-34-5678
###-##-#### | "012-34-5678" | 012-34-5678
(###) ###-#### | "2125551212" | (212) 555-1212
(###) ###*#### | "212-555-1212" | (212) 555-1212

Date Mask Type

Mask | Raw Data | Formatted Data
Ddd - Mmm d, yyyy | 9/22/1998 | Tue - Sep 22, 1998
m/d/yy | 9/22/1998 | 9/22/98
Mmmm d | 9/22/1998 | September 9

Special Note:

  • Raw Data shows dates in U.S. format. Depending on your Windows regional settings, raw data may be displayed in a different format.

Numeric and Currency Mask Type

Mask | Raw Data | Formatted Data
$#,##0;($#,##0) | 1001.536 | $1,002
$#,##0;($#,##0) | -1001.536 | ($1,002)
0.00 | -0.2 | -0.20
00000 | 123 | 00123
-> * <- | 104 | -> 104 <-
*0.* | 30.2500 | 30.25
+*0.00;-*0.00 | 12 | +12.00

Special Note:

  • Be careful with $, because only the currency symbol changes, not the value. For instance, the order entry example in the EURO countries shows the EURO symbol while in the USA it shows the DOLLAR and in England it shows a POUND sign.

See Also

Masks in Windows Applications


Transactions and DDOs

Transactions and database locking support is built directly into Data Dictionaries. Database updates are, therefore, always multi-user safe. Managing transactions, locking and multi-user access is automatic requiring no programming effort from the developer.

All database updates within a Data Dictionary occur within a transaction. When a save occurs an entire hierarchy of records may be created, updated and saved. When a delete occurs it is possible that many records, from many child tables will be deleted and that many parent table records will be updated. Both of these operations occur within a single transaction. If for any reason a transaction cannot be completed, the entire transaction is rolled back and no changes are committed to the database.

Locks will be placed on your database during a transaction. The tables that participate in the transaction will be write-locked. This allows other users to read data from the tables but not allow them to write data until the locks are released. Locks are released when a transaction has ended – either as a successful commit or a failed rollback.

The granularity of a lock depends on the database. Some databases such as the embedded DataFlex database will place locks on an entire table; other databases, such as Microsoft SQL and Pervasive.SQL will place locks on the records involved in the transaction. In either case, because transactions involve locking you want to make transactions as short as possible.

Data Dictionaries are designed to keep transactions short and small and there is little you have to do to assist in this process. You should be careful to never require user input during a transaction. Don’t ask questions once a save or delete has started.

Error Handling in Transactions

If an error is encountered during a transaction, the transaction is stopped. When an error occurs in a DDO during a transaction, two things happen:

  1. The DDO process is stopped and the transaction is rolled back

  2. The error message is not actually generated until after the rollback and the tables are all unlocked.

The deferral of the error message ensures that the error will not be reported while tables are locked.

The error can be expected (generated by the programmer) or unexpected (generated by the runtime or the database). In either case, the results are the same. If the error is generated by the programmer, it must be generated with the Error command. Only the error command triggers the proper transaction abort sequence and the proper deferral of error reporting.

When an error occurs, all processing of code stops. The runtime internally rolls back the transaction, cleans up its call stack and jumps to the end of the transaction. The remainder of the DDO messages are not sent and the remainder of any procedure or function code is not executed. In the following example, the code following the error command in Validate_Save will never get executed:

Function Validate_Save returns integer

Error 300 "We have an error"

// these two statements will never get executed.

Send DoSomething

Function_Return 1

End_Function

If you generate an error inside Validate_Save or Validate_Delete, there is no need to return a non-zero value. The process has already been halted.

Any Error anywhere within a transaction halts the transaction. This means that you can generate an error and stop the transaction inside of any of the save events (e.g. Validate_Save, Creating, Update, Backout, Save_Main_File) or delete events (e.g. Validate_Delete, Deleting, Backout, Delete_Main_File).

Transaction Groups

Multiple save and delete operations can be grouped within a single transaction. This is done by placing the transactions within a Begin_Transaction / End_Transaction command block. The saves and deletes within the transaction block may refer to different DDOs or perform multiple operations within the same DDO.

// Save changes in two different DDOs (handles of DDOs are passed)

Procedure SaveTransactions handle hoDDO1 handle hoDDO2

Begin_Transaction

Send Request_Save of hoDDO1

Send Request_Save of hoDDO2

End_Transaction

End_Procedure

// Delete all records for passed array of RowIds

Procedure DeleteRecords RowId[] DeleteIDs

Integer iRec iRecs iFile

Get Main_File of oSome_DD to iFile

Begin_Transaction

Move (SizeOfArray(DeleteIDs)) to iRecs

For iRec from 0 to (iRecs-1)

Send FindByRowId of oSome_DD iFile DeleteIds[iRec]

if (Found) ;

Send Request_Delete of oSome_DD

Loop

End_Transaction

End_Procedure

The purpose of grouping transactions is to make sure that the entire transaction is treated as a single entity. Therefore:

  • Either the entire transaction is committed or the entire transaction is rolled back.

  • Locks applied during the grouped transaction will remain in effect until the transaction is complete.

  • If any DDO save or delete within the transaction fails, the entire transaction is rolled back and program control is returned to the line following the End_Transaction command.

Grouped transactions should be used only when required. You always want to make keep transaction durations as short as possible. DDOs are designed to generate short transactions. Combining many short transactions into a group somewhat defeats the purpose. The above delete example, could create a very long transaction, which could interfere with other user’s database operations. Common sense needs to be applied when grouping transactions.


Alias Tables and DDOs

Application requirements arise where the same physical table needs to be used more than once within a single Data Dictionary object structure. This occurs when the same physical table serves more than one logical purpose. Some examples of this are:

  • A single order requires two addresses – a bill-to address and a ship-to address. Both of these address records are maintained in the same table. In this case, a child record relates to the same physical table two times.

  • You maintain a list of service technicians. Each technician is assigned a backup technician (who is called when the primary technician is unavailable). Both main and backup technician records are stored in a single table. In this case, a table must relate to itself.

  • A single order requires two addresses – a bill-to address and a ship-to address. Both of these address records have a parent record of their own (region) which will be different for each address record (bill-to region and ship-to region). In this case, a child record relates to the same physical parent table two times which, in turn, will relate to the same physical grandparent table two times.

In all of the above cases, each of the logical tables is unique and serves a distinct purpose in your table hierarchy. The last example would be structured as follows:

The tables, while logically distinct, need to be mapped to the same physical file.

A technique called alias tables supports this ability. An alias table is created by placing the same physical table in multiple locations in your Filelist. An alias Filelist entry is created using the same physical name as the main table (Root Name) while creating unique logical names (DFName and User Name). When the tables are opened within the same program, a separate file-buffer is created for each logical table. Data from each table is read and written to the same physical table.

Using alias tables creates certain multi-user challenges. The same physical table cannot be locked twice. To avoid this condition, you must identify logical tables sharing the same physical table as one master table and the remainder, an alias table (or tables). This identification technique is described below.

See Also

Alias Table Tips

Creating Alias Tables and Data Dictionaries

Setting Alias Table Modes

Setting Alias-File Relationships

Setting Data Dictionary Required-Parent Lists

Sample Alias Structures

Deleting an Alias Table


Alias Table Tips

  1. Once an alias table is created and a Data Dictionary for that alias is created, it is treated (almost) like a normal table. When Visual DataFlex builds your views, it will treat the alias table like any other table in your list.

  2. Try to keep the alias table simple. Ideally an alias table should be read-only. When a child record is saved, the child should avoid changing data in the alias (which can happen through the child Data Dictionary’s Update and Backout events).

  3. Master and alias tables will each have their own Data Dictionary and each of these DDs will be different. Usually, the alias Data Dictionary will be much simpler than the master Data Dictionary.

  4. Do not try to make master and alias tables share the same lookup object. If an alias table requires a lookup object, create a separate lookup object for the alias table.

  5. If you change the master table’s structure, you will also change the alias table’s structure. In such a case, you will need to reload the alias table’s Data Dictionary and make the appropriate changes.

  6. If you delete an alias table, you will also delete the master table.

  7. If alias tables are being written to during a save, you must be careful if the main and alias record happen to be the same physical record. If the record’s numerical balance is changed in the child’s Data Dictionary Update or Backout procedure (e.g., Move (Address2.OrderCount+1) to Address2.OrderCount), an inaccurate balance may be created. It is up to you to protect against this

  8. Keep in mind that the use of alias tables is an advanced technique. A typical developer may never need to use this technique. Avoid using alias tables if you do not really need them.

  9. Do not use this as a method to see multiple sets of records within the same view. For example, creating an alias table so you could view all open invoices and all closed invoices within the same view is an inappropriate use of this technique.

  10. You do not need to create an alias table to support diamond relationships. Consider the following example:

In a diamond relationship, the top of the diamond (A) will be the same parent for both child records (B and C). If a record is found in the grandchild (D), the relate process will result in same parent record in A regardless of the finding path (D to B to A, D to C to A). These types of diamond relationships are directly supported by Data Dictionaries. Using an alias for this is unnecessary.

See Also

Alias Tables and DDOs


Creating Alias Tables and Data Dictionaries

Documentation in Database Builder explains how to create an alias table and Data Dictionary. A summary of those steps is:

  1. Adding a new entry into Filelist.

  2. Creating an .FD table for the new alias.

  3. Opening the new alias table and creating a Data Dictionary.

  4. Setting the alias table’s and the main table’s attributes to the proper alias and master values

  5. Setting all additional relationships and relationship requirements.

See Also

Alias Tables and DDOs


Setting Alias Table Modes

Within your Data Dictionary you must identify this table as an alias table and identify the original table as a master table. This is done with the Set_Attribute DF_FILE_ALIAS command.

Set_Attribute DF_FILE_ALIAS of Alias_Table.File_Number to DF_FILE_IS_ALIAS

Set_Attribute DF_FILE_ALIAS of Main_Table.File_Number to DF_FILE_IS_MASTER

A typical code section might look like the following:

// You must tell DF about the Alias-Master condition

Open Customer// make sure that the main table is open

Set_Attribute DF_FILE_ALIAS Cust2.File_Number to DF_FILE_IS_ALIAS

Set_Attribute DF_FILE_ALIAS Customer.File_Number to DF_FILE_IS_MASTER

//

Class Cust2_DataDictionary is a DataDictionary

// Define_Fields:

// This procedure is used to set up all Data Dictionary rules.

Procedure Define_Fields

:

You must make sure you mark your tables as master and alias. Normally both of these settings can be made within the alias table’s Data Dictionary. This makes good sense because the main, non-alias, table does not need to be recognized as a master until an alias table is used. We add the open command to make sure that the master table is already opened (the table cannot be designated a master table if it is not open). This way, you do not have to worry about the ordering of tables in your program.

Note

If a table is closed and reopened, the DF_FILE_ALIAS attribute must be reapplied.

See Also

Alias Tables and DDOs


Setting Alias-File Relationships

Creating relationships to an alias table (where the alias is defined to be a related-to parent) is easy. After an alias table has been properly created, child tables may relate to the alias as they would to any other normal table. This is how alias tables are normally used.

If your master table relates to other tables (i.e., it is the child of a relates-to relationship) or your alias tables need to relate to a parent table, special care must be taken. Relationship information is stored in the physical table’s header. If a relationship is created in the master file, that relationship will also exist in the alias table. Similarly, if you change a relationship in the alias file, that change will also be made for the master table. You are changing the same physical table structure. Any change in one table affects the other.

Normally, you will not want a master and alias record to always point to the same physical parent. If relationships exist in the master table, you will want to clear them or change them in the alias. While this cannot be done by changing the relationship in the file, it can be done at runtime using the Set_Relate command. The Set_Relate command is “‘soft”. It only changes relationships at run time—which is exactly what is needed with alias tables. The Set_Relate command will be added to your Data Dictionary in the top of the methods section.

Clearing a Relationship in an Alias Table

An alias table will usually not require all of the same parent tables that its master-file counterpart does. Alias tables are often read-only and will have no required parents. When the alias table is opened, it inherits all of the relationships of its master. These can be cleared by using the Set_Relate command as follows:

Set_Relate Alias_File.Field_Name to |FN0,0

Where:

  • Alias_file.Field_Name is the name of the relating-from field to set.

  • FN0,0 is a special symbol that indicates that the relationship should be cleared.

Establishing a New Relationship in an Alias Table

Sometimes an alias table will need to relate to a different parent table. Most likely, the different parent table will be an alias table itself. The new relationship would be set using the Set_Relate command as follows:

Set_Relate Alias_File.Field_Name to ParentFile.Field_Name

Where:

  • Alias_file.Field_Name is the name of the relating-from field to set.

  • ParentFile.Field_Name is the name of the parent table and field to relate to.

See Also

Alias Tables and DDOs


Setting Data Dictionary Required-Parent Lists

If any new relationships are established, you must remember to adjust your Data Dictionary required-parent-table-structure list to reflect these changes. An alias table will almost always have a relating-to child table (this is why you created the alias table). If a hard or soft relationship is created between the child and the alias parent, this dependency must be added to the child’s required-parent list. If the alias table relates to a parent file, this dependency should be added to the alias table’s required-parent list.

See Also

Alias Tables and DDOs


Sample Alias Structures

Listed below are some typical alias table structures with sample Data Dictionaries required to support them. Those structures are:

  • Simple – a single alias table

  • Recursive – an alias table that relates to itself

  • Multi-Level – an alias table that has related parents which must also be defined as alias tables.

Simple Alias Structure

A single order requires two addresses—a bill-to address and a ship-to address. Both of these address records are maintained in the same physical table. The main address table is named Address. We will create an alias table named Address2. Assume that Address relates to a table named Region and that we will wish to remove this relationship in the alias. The steps for creating this alias table are:

  1. Add the alias, Address2, to your Filelist, create the .FD and create the alias DD file.

  2. In the Address2 methods section, define the master and alias tables and clear the existing table relationship. The methods section for the Address2 alias table will look like this:

// You must tell DF about the Alias-Master condition

Open Address // make sure that the main table is open

Set_Attribute DF_FILE_ALIAS Address2.File_Number to DF_FILE_IS_ALIAS

Set_Attribute DF_FILE_ALIAS Address.File_Number to DF_FILE_IS_MASTER

// clear the relationship in the alias file

Set_relate Address2.Region_key to |FN0,0

//

Class Address2_DataDictionary is a DataDictionary

:

  3. In the child table (Order) that relates to the Address2 alias file, create a “hard” (Database Builder) or soft (Set_Relate) relationship to this table.

  4. Make sure that both tables, Address and Address2, are added to the Order DD required-parent list.

Recursive Alias Structure

You maintain a list of service technicians. Each technician is assigned a backup technician (who is called when the primary technician is unavailable). The main table is Tech, the alias table will be named TechBkup. The steps for creating this alias table are:

  1. Add the alias, TechBkup, to your Filelist, create the .FD and create the alias Data Dictionary file.

  2. In the TechBkup methods editor, define the master and alias tables. The methods editor for the alias table will look like this:

// TechBkup

// You must tell DF about the Alias-Master condition

Open Tech // make sure that the main table is open

Set_Attribute DF_FILE_ALIAS TechBkup.File_Number to DF_FILE_IS_ALIAS

Set_Attribute DF_FILE_ALIAS Tech.File_Number to DF_FILE_IS_MASTER

//

Class TechBkup_DataDictionary is a DataDictionary

:

  3. In Tech create a soft (Set_Relate) relationship to the alias table. The must be a soft relationship because a hard relationship would appear in both the main and alias table. This would be done as follows:

// Tech

// define soft relationship to alias file

Set_relate Tech.Bkup_Tech to TechBkup.Tech_Key

//

Class Tech_DataDictionary is a DataDictionary

:

  4. Make sure that TechBkup is added to the Tech DD required-parent list.

Multi-level Alias Structure

A single order requires two addresses—a bill-to address and a ship-to address. Both of these address records have a parent record of their own (Region), which will be different for each address record. The main tables Address and Region already exist. We need to create two alias entries, Address1 and Region1, as follows:

  1. Add the alias, Address2, to your Filelist, create the .FD, and create the alias Data Dictionary file.

  2. Add the alias, Region2, to your Filelist, create the .FD, and create the alias Data Dictionary file.

  3. In the methods section for Region2, define the master and alias tables for Region. The methods section for the Region2 table will look like this:

// Region2

// You must tell DF about the Alias-Master condition

Open Region // make sure that the main table is open

Set_attribute DF_FILE_ALIAS Region2.File_Number to DF_FILE_IS_ALIAS

Set_attribute DF_FILE_ALIAS Region.File_Number to DF_FILE_IS_MASTER

//

Class Region2_DataDictionary is a DataDictionary

:

  4. In the methods section for Address2, define the master and alias tables and set a relationship between Address2 and Region2. The methods section for the Address2 table will look like this:

// Address2

// You must tell DF about the Alias-Master condition

Open Address // make sure that the main table is open

Set_Attribute DF_FILE_ALIAS Address2.File_Number to DF_FILE_IS_ALIAS

Set_Attribute DF_FILE_ALIAS Address.File_Number to DF_FILE_IS_MASTER

// set the relationship between address2 and region2

Set_relate Address2.Region_key to Region2.Region_key

//

Class Address2_DataDictionary is a DataDictionary

:

  5. In the Address2 Data Dictionary, add Region2 to the required parent list.

  6. In the child table (Order) that relates to the Address2 alias file, create a “hard” (Database Builder) or “soft” (Set_Relate) relationship to Address2.

  7. In the Order Data Dictionary, make sure that both tables, Address and Address2, are added to the required parent list.

See Also

Alias Tables and DDOs


Deleting an Alias Table

Remember that an alias and a master table share the same physical table. If you erase or delete an alias file, you will also erase or delete your main file. Normally you do not delete an alias table; you remove it from your Filelist. Database Builder provides instructions for removing an alias table.

See Also

Alias Tables and DDOs


Smart Table Locking in Data Dictionaries

Smart Table Locking is a technique available to Databases that use table-locking. Record locking Database, do not need to use this feature.

The default behavior of saving or deleting is to lock and reread all open tables in the Filelist for the time required to affect the save to disk. In a structure of many related tables, with many rereads in parent, and sometimes child, tables, this can disrupt saving or deleting activities by other users seriously (it does not inhibit finding or editing activities).

DDOs support a feature named “smart filemode” that can substantially improve the performance of saves and deletes. This is controlled by a DDO property named Smart_Filemode_State. When all DDOs in a Data Dictionary structure set this property to true, only the tables that will actually participate in the operation will be locked and reread. This can significantly improve performance on the workstation performing the operation, as well as on workstations not performing the operation at that particular time (there is less network traffic).

Basic Smart-Filemode Usage

It is very easy to implement smart filemode. When a save or delete occurs, the DDO performing this operation will scan all participating DDOs in the structure. If Smart_Filemode_State is true for all of these DDOs, the DDO will use smart-filemode locking.

By default, smart-filemode is enabled in Data Dictionary objects and no change is required. If for some reason your DDO structure cannot support smart-filemode, you may disable this by setting Smart_Filemode_State to false. You would set this inside the Define_Fields procedure in the subclass. You should avoid doing this, and should instead apply your efforts in creating a table structure that supports smart filemode.

Class Myfile_DataDictionary is a DataDictionary

Procedure Define_Fields

Forward Send Define_Fields

Set Main_File to MyFile.File_Number

Set Smart_Filemode_State to False

:

End_Procedure

:

End_Class

How Smart Filemode Works

When a save or delete request is sent to a DDO, the following occurs:

  • Check if smart filemode should be used.

  • The Smart_Filemode_State property of all participating DDOs is checked. If any DDO’s Smart_Filemode_State property is false, smart table locking will not be employed (and the following steps will not be executed).

  • Record the current filemodes of all tables.

  • The filemodes of all tables are recorded in an internal array.

  • Set filemodes of all tables to read-only.

  • Actually, the tables are set to the value of the Smart_Filemode_For_No_Lock property (which is DF_FILEMODE_READONLY by default), thus making them exempt from locking from the program in which this is happening.

  • Set all participating tables’ filemodes to read/write.

  • The message Reset_Filemodes_For_Lock is sent to all participating DDOs, where it sets the filemodes of participating tables to the value of the Smart_Filemode_For_Lock property (the original mode, by default). Any custom augmentations occur at this point (such as marking a system table for participation).

  • The save/delete itself proceeds.

  • Only the tables with filemode other than DF_FILEMODE_READONLY are locked and reread.

  • The filemodes of all tables are restored to their original states.

Smart Filemode and Alias Tables

Smart Filemode’s default behavior takes account of alias tables (filemode of alias or master_alias) that are participating in the save/delete. The master table is set to the mode of Smart_Filemode_For_Lock, but with the DF_FILEMODE_NO_LOCKS bit turned off (thus the master-alias table is always locked). The alias tables are all set according to whether they are participating in the save/delete, but with the DF_FILEMODE_NO_LOCKS bit turned on (thus alias tables are never locked). Master-alias tables are locked and reread, while participating alias tables are reread but not locked, which avoids a deadlock condition. Smart filemode uses the value of the table’s DF_FILE_ALIAS attribute to decide what to do.

The default value of Smart_Filemode_State is true. You can set the state false in individual Data Dictionary subclasses, or even DDOs. Keep in mind that smart filemode will not occur if any DDO participating in the save or delete has Smart_Filemode_State false.


Debugging DDOs

The Data Dictionary Inspector

The DataDictionary Inspector (DD_Debug.dg) is a small package utility that can be used to aid in the debugging of Data Dictionaries. It was created to make it easier to inspect and change the state of any DataDictionary object and to test the efficiency of your find constraints.

Adding the Data Dictionary Inspector to an Application

The DataDictionary Inspector can be added a windows-application test component or application. To do this:

  1. Load your component or program

  2. Adding the following line to the top area of your “Outer-Component Code” section.

Use DD_Debug.dg

  3. Compile your program or test component

  4. Run your program

Using the Data Dictionary Inspector

Once loaded this utility can be invoked from within any view by pressing the CTRL+D key.

Obtaining DDO Information

You can select and view information from any DDO within the current DDO structure. This is done by selecting a DDO from the “Current DD” combo-form, which contains a list of all DDs within the structure.

Each DD’s Current_Record, Changed_State, and Should_Save condition is displayed.

  • Current Record shows which record is current (this is the record that will be saved or deleted). Note that this record is not always the same record that appears in your record buffer.

  • Changed_State tells you if the current DDO has data that has been changed directly within the DDO. Should_Save looks at the DDO and its parent DDOs and indicates if there is any change anywhere within the structure.

For each Field you can see if data is changed, if the data is valid (as a regular and as a foreign field), and each field’s current data value.

An additional “Misc” page provides more information about a DDO such as required and attached Server and Client DDOs, attached DEOs, and other DDO properties.

Changing DDO Properties

There are a number of properties you can change within a DDO that will alter the way your application runs. This allows you to test out different property settings during development.

You can change the following:

  • You can dynamically change the Relates-to constraint settings within a DDO structure by selecting a table in the “Related To File” combo-form. This allows you to create and remove parent-child constraints in your structure. This changes the Constrain_File property.

  • You can set the Auto-Fill checkbox to see how this changes relates-to record finding. This changes the Auto_Fill_State property.

  • You can set the First-Rec Auto-Fill checkbox to determine if an auto-fill should fill from the first or last valid record. This changes the pbAutoFillFromFirst property.

  • You can set the Inherit Constraints checkbox that determines if a DDO should inherit constraints defined in parent DDOs. This changes the pbInheritConstraints property.

  • You can select the ordering (index) to be used for any find. If the value is Best (-1) the DDO uses what index is requested for a find. When a specific number is selected, that index is always used for all finds. This changes the Ordering property. This can be useful in determining what indexes should be used for a specific constrained find operation.

Note that changing these settings will actually change the way your component being tested operates.

Finding Records

You can find records by entering a record number and pressing the “Find” button. This is used to find a particular record and all parents.

You find perform Next/Previous finds by any index by pressing one of the VCR style Find Buttons. This is most useful when you wish to test constrained record finding.

Testing the Efficiency of Constrained Finds

You can find out if your record finding is optimized or not. This is a very important feature. When records are found a counter is maintained that keeps track of how many records are read and how many of those records are valid. The closer these values are to each other, the better your find optimization. Here is a useful technique:

  1. Press the “Clear Count” button.

  2. Close the panel, run your application and find a few records.

  3. Now re-invoke (ctrl+d) the Data Dictionary Inspector. Look at the Tests_Count and Found_Count values. You may be in for a surprise! If the values are very different, you may need to better optimize your find constraints.

The Data Dictionary Inspector presents this information by using two runtime global integers Constrain_Tests_Count and Constrain_Found_Count.

The Inspector provides an additional way to test for non-optimized finds by providing error messages when filtered records exceeds a threshold. Whenever a constrained find occurs, the DDO may need to find and test many records before it finds a valid record. Often this type of filtering is to be expected. However, at times you may have created a very inefficient filter that is going to create performance problems when deployed.

If you wish to enabled error reporting for inefficient constrained finds you need to check the “Report errors if more than N records are filtered” checkbox and setting an appropriate value for “N”. Every DDO find will now be tested. For any single find, if the number of records tested exceeds the limit you defined an error will be raised. This test occurs even if the Data Dictionary Inspector is not invoked allowing you to set your threshold and run your program. If too many records are being filtered, you will get a warning.

Testing for DDO Reentrancy Errors

By default the Data Dictionary Inspector reports DDO reentrancy problems. Many DDO methods are not reentrant. The messages that are not reentrant are Clear, Clear_All, Request_Save, Request_Delete, Request_Assign, Request_Find, Request_Superfind, Find, FindByRowId, and Find_By_Recnum. For example, you cannot send the Find message in the middle of a Request_Save process (even if the message is being sent to another DDO).

When these types messages are sent in a reentrant state, the normal behavior of the DDO is to ignore them. This creates a problem for testing. If you are sending these messages, you are probably doing so in error. Rather than performing whatever action you are expecting, the message is being silently ignored.

When the Data Dictionary Inspector is used, these re-entrant messages are reported back to you as errors. This will make it easy for you to find and correct these problems. If during any DDO operation a reentrant operation is encountered, an error will be presented containing all of the required debug information.

To disable this error check, un-check the “Report DD Reentrancy Errors".

Special Notes

  • The DataDictionary Inspector cannot be used with Web-application components (it’s a windows based application component).

  • Remember to remove the “use DD_Debug.dg” from your program when you are done testing it. If you leave it in place, you users might accidentally find it.

  • Note that this is a standard Studio dialog component. If you place DD_Debug.dg within a Workspace’s AppSrc area and register within the Studio you can load it, study it and edit it as a Studio dialog component.

  • If your application is already using the Ctrl+D key you will have to create an alternate On_Key command definition that sends the message “DebugDDs”. For example, to move this to Ctrl+F12 you would add this line of code to your application:

On_key Key_Ctrl+Key_F12 Send DebugDDs


Introduction to Data Dictionaries

In Visual DataFlex, your application business rules are expressed through and managed by Data Dictionaries. Data Dictionaries create a layer between your application logic and your data. This has the following advantages:

  • It lets your application interact more effectively with your database – Data Dictionaries provides your application with a wealth of information about your database that is not part of your physical database. It extends your database.

  • It protects your data – Data Dictionaries make sure that only valid data is added to your database.

  • It centralizes application logic – All of the information and rules about a Table is stored in a single place. If you need to make a change, you make a change in a single place and all parts of your application acquire the change.

Data Dictionaries are defined as classes. You will create a Data Dictionary class for each table in your database. These classes will be used while you are building your application and when you run your application.

Using Data Dictionaries while Building an Application

While you are building your application the Visual DataFlex development tools will use the information in your Data Dictionary classes to assist you in the development process. This information is used when you build Windows entry views, Reports and web pages. The Studio and its wizards will use the Data Dictionaries to determine:

  • What tables should be opened and how they should be connected

  • What kinds of controls should be used for a particular field (e.g., single line form, multi-line form, checkbox)

  • What the labels and context help text should be used for your controls

  • When lookup lists should be used, how it should be used, and how it will look.

Properly defined Data Dictionaries makes it easy to rapidly create good looking, solid and maintainable applications.

Using Data Dictionaries when Running an Application

Data Dictionaries are added to an application by creating Data Dictionary Objects (DDOs). At runtime, the DDOs serve two main purposes:

  • They coordinate database activity among data-entry objects (DEOs)

  • They provide database validation and update services to a program

These two purposes are distinct.

Creating a structure of DDOs, properly connecting the objects together and properly connecting DEOs to this structure provides coordinated database activity. These connections are all programmed in your components at the object level.

The database rules are maintained and enforced by created in your data dictionary class by setting properties and defining various Data Dictionary events. This is programmed at the class level.

Working with Data Dictionaries

Working with Data Dictionaries consists of:

  1. Creating a data dictionary subclass for each table in your database and coding database rules in these classes. This is done by setting properties and creating functions and procedures.

  2. Creating Data Dictionary Object structures inside of views, web objects and other components. A DDO structure is a group of DD objects that are connected to provide synchronized access to related tables.

  3. Creating objects (data entry objects) or methods (functions / procedures) inside your view, web object or other component that communicates with your DDOs. This allows you to view, create, edit or delete your data.

Building Data Dictionary Classes

You will create a Data Dictionary class for each table in your application. These classes, based on the DataDictionary class allow you to define information and set rules for a table. By placing these rules in a single place, a class, you will not have to repeat those rules in every component that accesses the table. The rules you can specify in this class will:

  • Define the table’s structure

  • Specify how it connects to other tables

  • Define what field properties and validations apply

  • Define what rules should be applied during saves, deletes and updates

  • Define other helper information such as label names and help text.

All data changes go through your Data Dictionary objects. Before data is changed in your database the Data Dictionaries validate your data using the rules, both simple and complex, that you build in your Data Dictionary classes.

Data dictionaries are such an important class that a special tool, Database Builder, is provided that allows you to create and maintain Data Dictionary classes visually. We expect that you will always this tool to maintain your Data Dictionaries. Database Builder creates the source code for these classes. In some cases this source is generated by selecting appropriate options from various Database Builder dialogs while in other cases you will create this source code yourself using Database Builder’s code editor.

Building classes is discussed in Defining Data Dictionary Classes.

Building Data Dictionary Object Structures

A set of related tables are represented you your application as a Data Dictionary Object (DDO) structure. DDO structures are created within various container objects that are designed to handle DDOs. Some of the container objects supported for this are:

  • dbView – These are used by windows applications for data entry

  • ReportView – These are used by windows application for reporting

  • BusinessProcess – These are used by windows and web applications to handle batch process updates

  • cWebBusinessProcess – These are used in Web Applications to provide all of the back end support for your Browser HTML pages

  • cWebService – These are used in Web Applications to provides support for web-services.

The rules for assembling DDO structures are the same for all of these containers. Each Data Dictionary object must be created and properly connected to the structure. This is done through child DDOs creating links to their parent DDOs.

When properly assembled DDO structures provide synchronized access to a full set of hierarchical data. Messages are propagated, as needed, between various Data Dictionary objects providing consistent and proper behaviors for your find, clear, save and delete operations. In additions, these structures are validated as being complete before you are allowed to change data.

The Studio handles the building of DDO structures for you.

Data Dictionary Object Structures is discussed in Creating Data Dictionary Object Structures.

Constraints and Filters

An additional job of Data Dictionary objects is to allow you to constrain and filter records within a component. Two types of constraints are supported:

  • When one DDO relates to another, you may want the child DDO to only show the records that relate to the current record in the related-to, parent DDO. This is called a relates-to constraint.

  • A view or report may only be concerned with a subset of records at any one time. You could, for example, specify customers and a given region and/or tax bracket. These are called Filter constraints.

Both of these kinds of constraints (and the can be combined) are defined inside of your DDO structures.

Constraints and Filters are discussed in Constraints and Filters.

Using the Data Dictionary Objects

The object that contains a DDO structure also contains all of the objects and methods that are used to communicate with those DDOs. This communication process is therefore completely encapsulated.

In some cases, this communication will occur between a data entry object (DEO) and a DDO. For example, a windows view will contain DEOs that allow you to view and edit your data. Each DEO within the view is assigned to a DDO and all communication between the DDO and the DEO are automatic.

In other cases, the communication will occur between a method (a procedure or function) and a DDO. For example, a business process object, a web browser object or a web service object will contain a DDO structure and methods that use the DDO.

In all cases, DDOs are used for the same purpose. They need to be able to:

  • Find or Clear data

  • Return information about a DDO field value

  • Make changes in a DDO’s field value.

  • Validate and Save data

  • Validate and Delete data

In a web application, the connection between your DDO and DEO (your browser) is indirect, or batched. All changes in a DDO are sent to the browser in HTML format as a single event. All changes in a DEO (the browser) are sent to the DDO as a single batch submission. Your Web Browser Object coordinates this activity.

The same DDO is capable of supporting different interfaces (e.g., windows controls, HTML pages, web services) and therefore the DDO's ability to interface with these interfaces will differ. However, the basic DDO logic and validation services are supported properly across all platforms. For example, field validations are always properly executed before a save.

Using Data Dictionary Objects is discussed in Using Data Dictionary Objects in your Components.

Using Data Dictionary Objects with Windows Applications

Windows applications contain a special kind of entry object that it closely integrated with Data Dictionary Objects. These are called data-entry objects (DEOs). Each DEO is bound to a Field in a table. In addition, each DEO specifies a Data Dictionary object to act as its server.

A windows view will consist of a DDO structure and a number of these DEOs. Messages, often triggered by user interaction, are sent from the DEO to its server DDO. The messages tell the DDO to perform one of the DDO tasks listed above (find, save, delete, etc.). When the DDO has completed the requested operation it will send notification messages to all of the connected DEOs. The DEOs will use these notifications to update their data and appearance. The synchronization between DEOs and DDOs allows these entry objects to be fully “data-aware”. By adding very little code, you can create sophisticated data entry applications.

Windows applications can also use DDOs within reports. A report will define a DDO to act as its server. All record finding will be handled automatically by messages sent from the report to the server DDO. The use of DDOs in reports is not required.

Windows applications also use batch process objects to handle custom updates. A Batch Process object must contain custom methods that perform the processes. You create the code in these methods that controls the activity between your DDO and the process. A complete Data Dictionary interface exists that allows you write code to perform the same kinds of tasks that the DEOs do for you automatically (e.g. clear, find, change data, save, delete).

Using Data Dictionary Objects with Web Applications

Web Applications use web objects to handle all processing. There are two types of web-objects:

  • Web Browser Objects (cWebBusinessProcess) – these are used to interact with browser based web-pages

  • Web Service Objects (cWebService) – these are used to provide web-services.

These objects are designed to contain DDO structures and methods that communicate with those DDOs. A developer interacts with the DDOs in the same manner they operate with batch processes in a windows application.

Web Browser Objects expect that the visual interface is provided by creating HTML pages. Those pages are created (programmed) using Active Server Pages (ASP). The ASP pages make calls into Web-Browser objects. Within the Web-Browser object you create methods to do whatever is needed. You then make this method available to your ASP page by publishing its the interface. In addition, Web-Browser objects contain a number of built-in interfaces that provide high-level access your Data Dictionaries. This allows you to perform all of the basic Data Dictionary functions (e.g., Find, Clear, Save, Delete) without having to write any code in your Web Browser Objects.

Web Service Objects provide web-service support. A web-service may or may not need to access Data Dictionaries. If they do, a DDO structure may be added to the Web-Service object and methods can be created that will communicate with the DDOs as needed.