Setup tables are normally created first as they are used on the main and transactional tables. Before a table is created, we must know which extended data types and enumerated types will be used or created.
The structure follows a common pattern, and you are welcome to create the tables based on the definitions described in the following section, using the detailed steps to check against. The tables and types created in the following section will be used in the upcoming chapters.
The vehicle group table is a Group
table, which is in common use in AX, and most main tables have a group field to assist in reporting, filtering, and other requirements. The definition is given here:
Table |
Label |
Table group | |
---|---|---|---|
|
Vehicle groups |
Group | |
|
|
|
|
Field name |
Type |
Size |
EDT |
|
String |
10 |
|
|
String |
60 |
|
The table is prefixed with ConFMS
so that we can easily see the objects that are specific to this solution. For smaller ad hoc changes, it may be sufficient to add the organization prefix. The fields are not prefixed as they are in "our" object.
The VehicleGroupId
field is a standard field type, and will almost always be a string of length 10. It will have its own EDT, but will extend the standard group EDT of SysGroup
. This is an EDT that most group EDTs extend, such as the customer, item, and vendor group EDTs.
Description
is a standard EDT, and we will use this only when we create the table. There are several EDTs that we will see reused on many tables, such as Name
and Description
.
To create the ConFMSVehicleGroupId
EDT, follow these steps from within the MorphX IDE:
ConFleetManagementSystem
project, which we created in the last chapter.Type1
to ConFMSVehicleGroupId
. You can use the option Rename from the context / right-click menu, but F2 is quicker.Vehicle group
and create a label.SysGroup
. Note that as soon as you enter SysGr
, AX will enter the rest for you.The EDT is not yet complete. We need to create the EDT's primary table, the vehicle group table.
The group tables are a common table type, and requires at least two fields, a group field and a description field.
There are some table properties that we will need to complete before we move on to creating the two methods. To create the table definition, follow these steps:
ConFMSVehicleGroup
under the Tables node in your project.Vehicle groups
, and create a label as we did earlier.ModifiedDateTime
, ModifiedBy
, CreatedDateTime
, and CreatedBy
tracking fields.Next, we need to add our two fields, which can be done by right-clicking on the Fields node, but they are much quicker to create by dragging and dropping. To do this, follow these steps:
ConFMSVehicleGroup
, drag the ConFMSVehicleGroupId
EDT onto top of the Fields node, and rename this to VehicleGroupId
Yes
, set AllowEdit to No
, and leave AllowEditOnCreate as Yes
.VehicleGroupId
.Yes
, set AllowEdit to No
, and leave AllowEditOnCreate as Yes
.Since we will leave AllowEditOnCreate as Yes
, we can enter the ID but not change it afterwards; this helps enforce referential integrity. It can still be changed using the Rename primary key function, but this should still be used with care after consultation with a developer or technical consultant.
Description
EDT.Description
onto your table's fields list.VehicleGroupId
and TitleField2 to Description
. AX can show these fields on forms that have this table as a data source.To aid the user interface design, we can place our fields in groups. This helps us provide a consistent interface for our users and also provides better and centralized control over the UI.
We only need one field group, as there is only a grid view of two fields. We will call this Overview
. The following steps will create the field group:
Overview
.Overview
.VehicleGroupId
is first, using either the mouse or Alt and the up arrow key.There are two other key field groups, AutoLookup
and AutoReport
. The AutoLookup
field group governs the fields that appear when the system constructs a drop-down list against the table. The AutoReport
field group governs the fields that appear should the user choose to create a quick report for the table. For this table, add all the fields to these two groups.
The next part is to set up indexes. We set up indexes both for performance and to enforce unique constraints. There is another more fundamental reason. You may have noticed that the primary and clustered indexes were SurrogateKey
. This is the default index, based on the record ID (RecId
). Should we add a primary key relation, it will be based on that index and therefore on the RecId
field. A record ID foreign key is of little use to us, as we intend the vehicle group ID to be placed on our vehicle table for grouping and reporting purposes.
So, we need to create a unique index based on the VehicleGroupId
field, and tell the table to use this as its primary key. To create the primary index, follow these steps:
Index1
to VehGroupIdx
.VehicleGroupId
field onto VehGroupIdx
to add this field to the index.VehGroupIdx
index and change the AllowDuplicates property to No
and the AlternateKey property to Yes
.ConFMSVehicleGroup
table.PrimaryIndex
and ClusteredIndex
to VehGroupIdx
.The final step before we hook up the user interface is to add a find
and an exists
method. These aren't used by the system, but it is a convention to create these methods on the tables we create. As the table's authors, we understand how to find records on this table and this saves time later as we avoid writing select
statements to find the record.
These methods follow the same pattern for all tables we create, and are based on the primary key field, or fields. In our case, we have the VehicleGroupId
key field, which is based on the ConFMSVehicleGroupId
EDT (type).
The find
method allows us to locate a record based (usually) on its primary key, and choose whether or not it should be selected for an update (locked). The method is a static method, which means we don't need to instantiate the table before calling it.
First, to create the find method, right-click on the Methods node and choose New Method. This will show a method template:
private void method1() { }
Methods marked as private
are only visible for calls within this table. Our method is designed to be called from other objects and these methods are also declared as static
, which is done as follows:
public static void method1() { }
The next part is the type of object the method returns. The method template assumes to be void
. We want to return a record of the type ConFMSVehicleGroup
, so replace void
with the table name, but watch what happens as you type.
Replace void
with the table name, but watch what happens as you type:
Use the arrow keys to choose the appropriate type from the pop-up list. In this case, it is highlighted, so just press Enter.
Rename the method to find
, and specify the parameters that the method accepts: the primary key field and whether or not the record should be selected for update.
These are specified as follows:
public static ConFMSVehicleGroup find( ConFMSVehicleGroupId _groupId, boolean _forUpdate = false) { }
We now have a vehicle group and know whether we should lock the record. We can now write the method's content.
The pattern for this method is similar each time we use it, and is done by:
_forUpdate
parameter to mark the table as for updateselect
statement to select the recordTo save making a needless select call, we can also make the call only if the vehicle group is not empty. The final code will be like this:
public static ConFMSVehicleGroup find(ConFMSVehicleGroupId _groupId, boolean _forUpdate = false) { ConFMSVehicleGroup vehicleGroup; vehicleGroup.selectForUpdate(_forUpdate); if(_groupId != "") { select firstonly vehicleGroup where vehicleGroup.VehicleGroupId == _groupId; } return vehicleGroup; }
The firstonly
keyword tells SQL to select only the first record it finds, which is the same as TOP 1
in Transact-SQL. It doesn't make a great deal of difference here because we are searching the primary key. Nevertheless, if we intend to fetch only the first record, it gives a performance boost.
The exists
method is similar but returns true
or false
, depending on whether the record was found. The way to determine whether a record was found is to check whether the record ID is zero or not.
To improve performance, we will select only the RecId
column from the table. The method should look like this:
public static boolean exist(ConFMSVehicleGroupId _groupId) { ConFMSVehicleGroup vehicleGroup; if(_groupId != "") { select RecId from vehicleGroup where vehicleGroup.VehicleGroupId == _groupId; } return (vehicleGroup.RecId != 0); }
Close the code editor and press Save All.
Aside from some user interface elements, our table is complete. You can browse the data in it using the table browser by choosing Open from the table's context menu on the table.
You will notice some system-generated fields here, which are as follows:
There is one key step remaining to complete the ConFMSVehicleGroupId
EDT, that is, to tell the system how it relates to the vehicle group table.
Whenever we add an EDT that is also the primary key of a table, we need to give AX some information; this pays dividends later. To complete this setup, follow these steps:
ConFMSVehicleGroupId
EDT.ConFMSVehicleGroup
.ConFMSVehicleGroup
node and right-click on Table references.ConFMSVehicleGroup
. Set the RelatedField property to VehiceGroupId
.AX will now understand that the ConFMSVehicleGroup
EDT should be a foreign key to this table, and also will offer a drop-down list of vehicle groups.
A table index is a structure designed to make finding a specific record as fast as possible. The index is an ordered collection of keys and pointers to the actual records. Finding a record matching a given key involves going to the appropriate location in the index where that key is stored. Then you have to follow the pointer to the location of the actual record and the rest of the data. This, of course, requires two operations, an index seek and a lookup to get the actual data.
When we search for a record, SQL Server is able to determine the best index, or indexes, to use for that particular query. If we realize that we often require the same set of fields from a specific query, we can create an index that contains the keys we wish to search on and the fields we wish to fetch. This improves performance considerably, as SQL will use that index and can then simply return the values that already exist in the index.
We can improve this further by marking the fields we simply wish to return as IncludedColumn (a property of the fields in an AX index). So in our case, we may wish to select the description from the vehicle table where the vehicle group is Artic
. Therefore, a solution can be to add the Description
field to our VehGroupIdx
index and mark it as IncludedColumn. However, there is a better solution in this case, which is to use clustered indexes.
A clustered index is similar to this, but the clustered index will contain the entire record, avoiding a lookup in the data for any field in the table. Clustered indexes are sorted by their keys; since the index contains the entire record, it can add a significant load to SQL Server if records are inserted, as opposed to being appended at the end of the table.
For setup tables where the number of records is small and changes infrequently, this isn't a problem and the read benefit far outweighs any drawback. For transactional tables, we must be careful. We should always have a clustered index, but the key must be sequential and the records must be added at the end of the table. In any case, we must have a clustered index.
An example of this is the sales order table, which has a clustered index based on SalesId
. This is a great choice as we will often use this key to locate a sales order record, and the field is also controlled by a number sequence; records should always be appended at the end. However, should we change the number sequence so that records are inserted "mid-table", we will experience a delay in inserting records, adding an unnecessary load to SQL Server.
The next table will hold the various service schedules for the vehicles we have. This will be a simple, linear schedule of the distance or elapsed days. We could simply have this information held against the vehicle directly, but since this type of information will be the same for a make and model of vehicle, it is better to hold the schedule as a related record. In our solution, we will select the service schedule for each vehicle.
This is a common design decision, do we assign this directly to the entity, via a rule set, or by some other criteria? Perhaps the service schedule should be implied by other properties, for example, make and model year. This could be equally restrictive, so another option can be to limit the choices to a valid option when creating the vehicle.
The table we will create is as follows:
Table |
Label |
Table group | |
---|---|---|---|
|
Service schedule |
Group | |
|
|
|
|
Field name |
Type |
Size |
EDT |
|
String |
10 |
|
|
String |
60 |
|
|
Integer |
| |
|
Integer |
|
This time, we will just list the steps required:
ConFMSVehicleSchedId
EDT as a String type, extending SysGroup
with Label as Service schedule
.ConFMSDistance
EDT as an Integer
type, and set Label to Distance
.ConFMSVehicleSchedule
table:Service schedules
.Group
.ConFMSVehicleSchedId
, Description
, ConFMSDistance
, and Days
.VehicleScheduleId
and Description
.VehicleScheduleIdx
. Add the VehicleScheduleId
field.No
and AlternateKey to Yes
.Overview
(with a label as before) with the VehicleScheduleId
and Description
fields.Schedule
and create a label and add the Distance
and Days
fields.find
and exist
methods as per these code samples:public static ConFMSVehicleSchedule find( ConFMSVehicleSchedId _schedId, boolean _forUpdate = false) { ConFMSVehicleSchedule vehSched; vehSched.selectForUpdate(_forUpdate); if(_schedId != "") { select vehSched where vehSched.VehicleScheduleId == _schedId; } return vehSched; } public static boolean exist(ConFMSVehicleSchedId _schedId) { ConFMSVehicleSchedule vehSched; if(_schedId != "") { select RecId from vehSched where vehSched.VehicleScheduleId == _schedId; } return (vehSched.RecId != 0); }
After some time, creating tables like this becomes almost muscle memory, and we can do it without thinking. Moreover, if you learn the keyboard shortcuts, it becomes very quick.
We can create code snippets for the find
and exists
methods; code snippets are covered in Appendix C, Code Editor Scripts.
18.216.27.251