Statements
X++ statements specify object state and object behavior. Table 3
provides examples of X++ language statements that are commonly found in
many programming languages.
Table 3. X++ Statement Examples
Statement | Example |
---|
assignment statement | int i = 42; ; i = 1; i++; ++i; i--; --i; i += 1; i -= 1;
|
compound statement |
|
print statement | int i = 42; ; print i; print "Hello World"; print 5.2; pause;
|
if statement | boolean b = true; int i = 42; ; if ( b == true ) { i++; } else { i--; }
|
break statement | int i; ; for ( i = 0; i < 100; i++ ) { if ( i > 50 ) { break; } }
|
continue statement | int i; int j = 0; ; for( i = 0; i < 100; i++ ) { if ( i < 50 ) { continue; } j++; }
|
while statement | int i = 4; ; while ( i <= 100 ) { i++; }
|
do while statement | int i = 4; ; do { i++; } while ( i <= 100 );
|
for statement | int i; ; for ( i = 0; i < 42; i++ ) { print i; pause; }
|
switch statement | str s = "test"; ; switch ( s ) { case "test" : print s; break; default : print "fail"; } pause;
|
pause statement | print "Hello World"; pause;
|
window statement | window 100, 10 at 100,10; print "Hello World"; pause;
|
breakpoint statement | breakpoint; //Causes the debugger to be invoked
|
return statement |
|
throw statement | throw error("Error text");
|
try statement | try { throw error("Force exception"); } catch( exception::Error ) { print "Error"; pause; } catch { print "Another exception"; pause; }
|
retry statement | try { throw error("Force exception"); } catch( exception::Error ) { retry; }
|
.NET CLR interoperability statement | System.Text.StringBuilder sb; sb = new System.Text.StringBuilder(); sb.Append("Hello World"); print sb.ToString(); pause;
|
local function | static void myJob(Args _args) { str myLocalFunction() { return "Hello World"; } ; print myLocalFunction(); pause; }
|
system function | guid g = newguid(); ; print abs(-1);
|
flush statement | MyTable myTable; ; flush myTable;
|
changecompany statement | MyTable myTable; ; while select myTable { print myTable.myField; } changecompany("ZZZ") { while select myTable { print myTable.myField; } } pause;
|
Data-Aware Statements
The
X++ language has built-in support for querying and manipulating
database data. The syntax for database statements is similar to
Structured Query Language (SQL), and this section assumes that you’re
familiar with SQL. The following code shows how a select statement is used to return only the first selected record from the MyTable database table and how the data in the record’s myField field is printed.
static void myJob(Args _args) { MyTable myTable; ; select firstOnly * from myTable where myTable.myField1=='value'; print myTable.myField2; pause; }
|
The “* from” part of the select statement in the example is optional. You can replace the asterisk (*) character with a comma-separated field list, such as myField2, myField3.
You must define all fields, however, on the selection table model
element, and only one selection table is allowed immediately after the from keyword. The where expression in the select statement can comprise any number of logical and relational operators. The firstOnly keyword is optional and can be replaced by one or more of the optional keywords. Table 4 describes all the possible keywords.
Table 4. Keyword Options for select Statements
Keyword | Description |
---|
Firstfast | Fetches the first selected record faster than the remaining selected records. |
Firstonly
Firstonly1 | Returns only the first selected record. |
Firstonly10 | Returns only the first 10 selected records. Supported only on the Oracle database platform. |
Firstonly100 | Returns only the first 100 selected records. Supported only on the Oracle database platform. |
Firstonly1000 | Returns only the first 1000 selected records. Supported only on the Oracle database platform. |
Forupdate | Selects records for updating. |
Nofetch | Specifies
that the Dynamics AX runtime should not execute the statement
immediately because the records are required only by some other
operation. |
Forceplaceholders | Forces
the Dynamics AX runtime to generate a query with placeholder field
constraints. For example, the query generated for the preceding code
example looks like this: select * from myTable where myField1=?. Database query plans are reused when this option is specified. This is the default option for selectforceliterals keyword. statements that don’t join table records. This keyword can’t be used with the |
Forceliterals | Forces
the Dynamics AX runtime to generate a query with the specified field
constraints. For example, the query generated for the preceding code
example looks like this: select * from myTable where myField1=’value’. Database query plans aren’t reused when this option is specified. This keyword can’t be used with the forceplaceholders keyword. |
Forceselectorder | Forces
the Microsoft SQL Server query processor to access tables in the order
in which they are specified in the query. (The Oracle query processor
ignores this keyword.) |
Forcenestedloop | Forces
the SQL Server query processor to use a nested-loop algorithm for table
join operations. Other join algorithms, such as hash-join and
merge-join, are therefore not considered by the query processor. |
Reverse | Returns records in reverse of the select order. |
Crosscompany | Forces the Dynamics AX runtime to generate a query without automatically adding the where clause in the dataAreaId field. This keyword can be used to select records from all or from a set of specified company accounts. For example, the query
while select crosscompany:companies myTable { }
selects all records in the myTable table from the company accounts specified in the companies container. |
optimisticlock | Overrides the table’s OccEnabled property and forces the optimistic locking scheme. This keyword can’t be used with the pessimisticlock and repeatableread keywords. |
Pessimisticlock | Overrides the table’s OccEnabled property and forces the pessimistic locking scheme. This keyword can’t be used with the optimisticlock and repeatableread keywords. |
Repeatableread | Locks
all records read within a transaction. This keyword can be used to
ensure consistent data is fetched by identical queries for the duration
of the transaction, at the cost of blocking other updates of those
records. Phantom reads can still occur if another process inserts
records that match the range of the query. This keyword can’t be used
with the optimisticlock and pessimisticlock keywords. |
The
following code example demonstrates how a table index clause is used to
suggest the index that a database server should use when querying
tables. The Dynamics AX runtime appends an order by clause and the index fields to the first select
statement’s database query. Records are thus ordered by the index. The
Dynamics AX runtime can insert a query hint into the second select statement’s database query, if the hint is reasonable to use.
static void myJob(Args _args) { MyTable1 myTable1; MyTable2 myTable2; ; while select myTable1 index myIndex1 { print myTable1.myField2; }
while select myTable2 index hint myIndex2 { print myTable2.myField2; } pause; }
|
The following code example demonstrates how the results from a select query can be ordered and grouped. The first select statement specifies that the resulting records must be sorted in ascending order based on myField1 values and then descending order based on myField2 values. The second select statement specifies that the resulting records must be grouped by myField1 values and sorted in descending order.
static void myJob(Args _args) { MyTable myTable; ; while select myTable order by Field1 asc, Field2 desc { print myTable.myField; } while select myTable group by Field1 desc { print myTable.Field1; } pause; }
|
The following code demonstrates use of the avg and count aggregate functions in select statements. The first select statement averages the values in the myFieldmyField field. The second select statement counts the number of records the selection returns and assigns the result to the myField column and assigns the result to the field.
static void myJob(Args _args) { MyTable myTable; ; select avg(myField) from myTable; print myTable.myField;
select count(myField) from myTable; print myTable.myField; pause; }
|
Caution
The
compiler doesn’t verify that aggregate function parameter types are
numeric, so the result the function returns could be assigned to a field
of type string. The compiler also performs rounding if, for example, the average function calculates a value of 1.5 and the type of myField is an integer. |
Table 5 describes the aggregate functions supported in X++ select statements.
Table 5. Aggregate Functions in X++ select Statements
Function | Description |
---|
avg | Returns the average of the non-null field values in the records the selection returns. |
count | Returns the number of non-null field values in the records the selection returns. |
sum | Returns the sum of the non-null field values in the records the selection returns. |
minof | Returns the minimum of the non-null field values in the records the selection returns. |
maxof | Returns the maximum of the non-null field values in the records the selection returns. |
The following code example demonstrates how tables are joined with join conditions. The first select statement joins two tables by using an equality join condition between fields in the tables. The second select statement joins three tables to illustrate how you can nest join conditions and use an exists operator as an existence test with a join condition. The second select statement also demonstrates how you can use a group
by sort in join conditions. In fact, the join condition can comprise multiple nested join conditions because the syntax of the join condition is the same as the body of a select statement.
static void myJob(Args _args) { MyTable1 myTable1; MyTable2 myTable2; MyTable3 myTable3; ; select myField from myTable1 join myTable2 where myTable1.myField1=myTable2.myField1; print myTable1.myField;
select myField from myTable1 join myTable2 group by myTable2.myField1 where myTable1.myField1=myTable2.myField1; exists join myTable3 where myTable1.myField1=myTable3.mField2; print myTable1.myField; pause; }
|
Table 6 describes the exists operator and the other join operators that can be used in place of the exists operator in the preceding example.
Table 6. Join Operators
Operator | Description |
---|
exists | Returns true if any records are in the result set after executing the join clause. Returns false otherwise. |
notexists | Returns false if any records are in the result set after executing the join clause. Returns true otherwise. |
outer | Returns the left outer join of the first and second tables. |
The following example demonstrates use of the while select statement that increments the myTable variable’s record cursor on each loop.
static void myJob(Args _args) { MyTable myTable; ; while select myTable { Print myTable.myField; } }
|
You must use the ttsbegin, ttscommit, and ttsabort transaction statements to modify records in tables and to insert records into tables. The ttsbegin statement marks the beginning of a database transaction block; ttsbegin-ttscommit transaction blocks can be nested. The ttsbegin statements increment the transaction level; the ttscommit
statements decrement the transaction level. The outermost block
decrements the transaction level to zero and commits all database
inserts and updates performed since the first ttsbegin statement to the database. The ttsabort statement rolls back all the database inserts, updates, and deletions performed since the ttsbegin statement. Table 7
provides examples of these transaction statements for single records
and operations and for set-based (multiple-record) operations.
Table 7. Transaction Statement Examples
Statement Type | Example |
---|
ttsbegin
ttscommit
ttsabort | boolean b = true; ; ttsbegin; if ( b == true ) ttscommit; else ttsabort;
|
select forupdate | MyTable myTable; ; ttsbegin; select forupdate myTable; myTable.myField = "new value"; myTable.update(); ttscommit;
|
insert method | MyTable myTable; ; ttsbegin; myTable.id = "new id"; myTable.myField = "new value"; myTable.insert(); ttscommit;
|
update_recordset | MyTable myTable;Int64 numberOfRecordsAffected; ; ttsbegin;
update_recordset myTable setting myField1 = "value1", myField2 = "value2" where myTable.id == "001"; numberOfRecordsAffected = myTable.RowCount(); ttscommit;
|
insert_recordset | MyTable1 myTable1; MyTable2 myTable2; Int64 numberOfRecordsAffected; ; ttsbegin; insert_recordset myTable2 ( myField1, myField2 ) select myField1, myField2 from myTable1; numberOfRecordsAffected = myTable.RowCount(); ttscommit;
|
delete_from | MyTable myTable; Int64 numberOfRecordsAffected; ; ttsbegin; delete_from myTable where myTable.id == "001"; numberOfRecordsAffected = myTable.RowCount(); ttscommit;
|
The last example in Table 7 demonstrates the method RowCount,
which is new in Dynamics AX 2009. Its purpose is to get the count of
records that are affected by set-based operations, namely, insert_recordset, update_recordset, and delete_from.
RowCount
facilitates application scenarios that use set-based update operations.
If no records are impacted by an update operation, the method
application performs an insert or a set-based insert. In such a
scenario, the application checks RowCount to see whether the update_ recordset statement impacted any rows. In the absence of RowCount,
the application does another round-trip to the database to get the
count of records impacted by the set-based update, an extra step that
can degrade performance.