Sunteți pe pagina 1din 53

X++ Coding Standards

General principles are:

Declare variables as locally as possible.

Check the error conditions in the beginning; return/abort as early as possible.

Have only one successful return point in the code (typically, the last statement), with
the exception of switch cases, or when checking for start conditions.

Keep the building blocks (methods) small and clear. A method should do a single,
well-defined job. It should therefore be easy to name a method.

Put braces around every block of statements, even if there is only one statement in
the block.

Put comments in your code, telling others what the code is supposed to do, and what
the parameters are used for.

Do not assign values to, or manipulate, actual parameters that are "supplied" by
value. You should always be able to trust that the value of such a parameter is the
one initially supplied. Treat such parameters as constants.

Clean up your code; delete unused variables, methods and classes.

Never let the user experience a runtime error. Take appropriate actions to either
manage the situation programmatically or throw an error informing the user in the
Infolog about the problem and what actions can be taken to fix the problem.

Never make assignments to the "this" variable.

Avoid dead code. (See Dead Code Examples.)

Reuse code. Avoid using the same lines of code in numerous places. Consider moving
them to a method instead.

Never use infolog.add directly. Use the indirection methods: error, warning, info and
checkFailed.

Design your application to avoid deadlocks.

More specific code standards are described below:

X++ layout

Comments

Semicolons

Constants

Arrays

Dates

try/catch statements

throw statements

ttsBegin and ttsCommit

if ... else and switch statements

select Statements

X++ Layout
General Guidelines

Only one statement per line.

Break up complex expressions that are more than one line - make it visually clear.

Use a single blank line to separate entities.

Do not use parentheses around the case constants.

Do not use parentheses around where expressions.

Add one space between if, switch, for, while and the expressions starting
parentheses. For example:
if (creditNote)

Use braces around all code blocks, except for around case clauses in a switch
statement. Use braces even if there is only one statement in the block.

Indentation
An indentation is equivalent to four (4) characters, which corresponds to one tab in the X++
editor. You must not start a new line in columns 2, 3 or 4.

Put opening and closing braces, { and }, on the same level, on separate lines, and
aligned with the command creating the block. They must be at the start of a line, and
in a tab column position (1, 5, 9 etc.). Braces must be on a dedicated line unless
a opening brace is followed by a semicolon ( {; ) or a closing brace is followed by a
while ( }while ).

The following reserved words should be placed at the beginning of a line: case, catch,
changeCompany, continue, default, else, for, if, retry, return, switch, try, ttsAbort,
ttsBegin, ttsCommit, while.
The exceptions to this rule are:
case: (reserved words in a case statement)
default: (reserved words in a default statement)
else if
}while

If a line of code starts with any other reserved word, or with an alphabetical
character, the line should start in a tab column position (1, 5, 9 etc). The following
reserved words must be placed in a tab column position: case, catch,
changeCompany, continue, default, else, for, if, retry, return, switch, try, ttsAbort,
ttsBegin, ttsCommit, while.
The exceptions to these rules are:
case: (reserved words in a case statement)
default: (reserved words in a default statement)
else if
}while

The reserved word else must have a dedicated line unless you write else if

switch-case statements: indent case and default statements by 1 level (with any
code within these indented a further level) and indent break statements by two
levels.

Indent where and other qualifiers to the select statement by one level.

If Booleans are used in a long expression, put them at the start of an indented new
line.

Example switch-case Statement


Copy

switch (myEnum)
{
case ABC::A:
...
break;
case ABC::B
...
break;
default:
...
break;
}

Example select Statement


Copy
select myTable
index hint myIndex
where myTable.field1 == 1
&& myTable.field2 == 2;

Example Layout of Booleans in a Long Expression


Copy
select firstOnly utilElements
where utilElements.recordType == recordType
&& utilElements.parentId == parentID
&& utilElements.name == elementName;

Column Layout
Column layout should be used where more than one line has the same structure; for
example, in declarations or assignments.
Do not use column layout when there is only one row, or if consecutive rows do not have the
same structure.
Column format is defined using extra blanks.

Example Column Layout


Copy
tmpABC.refRecId
= inventTable.recId;
tmpABC.itemGroupId = inventTable.itemGroupId;
tmpABC.itemId
= inventTable.itemId;
tmpABC.amount
= amount;
tmpABC.oldValue
= this.getCategory(inventTable);
tmpABC write();

Layout for Methods

The starting parenthesis on method declarations and calls should be the character just after
the method name (no space).
If there are one or two parameters, the parameters can be listed on the same line. If there
are more than two parameters, move each parameter onto a new line, and indent by 4
spaces.

Example Layout for Method with One or Two Parameters


Copy
myMethod(parameter1, parameter2);

Example Layout for Method with Many Parameters


Copy
myMethod(
parameter1,
parameter2,
parameter3);

X++ Standards: Comments


Use // for both single and multiline (block) comments. There should be a space between the
"//" and the start of the comment.
Comments should be in US-English and start with an uppercase letter (unless the first word
is a lowercase name).
Put comments on a separate line before the code they are describing. The only exception to
this is when you are describing parameters. In this case, put one parameter per line, with
the comment describing it to the right of the parameter.
When creating a multiline comment, do not write on the first or the last line of the comment
(as shown in the following example).
For example:
// Single line comment
<code>
//
// Comment on multiple lines.
// Do not add text to 1st or last line of comment.
//
<code>

Comments should not include:

Dates

Names

Aliases

Version or layer references

Bug numbers unless it is a workaround, or unless the code could appear


inappropriate if you didn't know that it was for a bug fix.

Politically or culturally sensitive phrases

Note
If you put a comment at the start of the method to describe its purpose and use, you can
use block comments (/* */).

Remove Commented-out Code from the


Application
The Best Practice is that we don't ship a product with commented-out code, so any
commented-out code should be removed.

Tip
To find comments in the source (both // .. and /* .. */), use the Find dialog to search for
methods containing the text (regular expression): /[/\*]

X++ Standards: Using Semicolons


Always place a semicolon (;) on an empty line in front of the first statement in your code
(after the variable declarations).
This is particularly important if the statement does not begin with a keyword (select, while,
and so on). For example, the first part of the statement is a variable or a type reference.
You should use a semicolon even if the code compiles. Types introduced later (that have the
same name as the first part of the statement) might prevent the code from compiling.

X++ Standards: Constants

Follow the best practices rules about using constants. These are designed to make it easier
to maintain X++ code.

Constants
Rule

Error
level

Do not use hard-coded constants (except 0, 1, 100).

Warning

Define constants in a method, class declaration, or if necessary globally in a

None

macro. Reuse existing constants.


Consider alternative ways of getting the constant:

Intrinsic Functions

maxInt, minInt, funcName, maxDate system functions

Global macros

User Interface Text


Rule

Error
level

User interface text must be in double quotes, and you must always use a label

Error

(also in double quotes).

User interface labels must be complete sentences. Do not build sentences using
more than one label, or other constants or variables under program control (do not
use concatenation).

None

Example:
Description description = "@SYS12345"
Use strFmt to format user interface text.

System-oriented Text
Rule

Error
level

System-oriented text constants must be in single quotes.

None

Do not hard-code text.

Warning

Do not use labels. You will get a warning if a label is used inside single

Warning

quotes.Example:
Copy
#define.Filename('myFile.txt')
Filename filename = #Filename;

Numeric Constants
Rule

Error
level

Always review the direct use of numeric constants, except for 0 meaning null, 1

None

meaning increment, and 100 when calculating percents and currencies.

Certain numeric constants are predefined, such as the number of days per week,
and the number of hours per day. For example, see the TimeConstants and

None

SysBitPos macros in the Application Object Tree (AOT).

X++ Standards: Arrays


This topic describes the best practice for using the memory option in arrays. For more
information, see Arrays.
The memory option is the optional second array declaration option. It specifies how many
consecutive entries in the array will be held in memory at a particular time. The rest will
reside on disk (a cached temporary file, indexed by the array index).
Dynamic arrays are sized according to the maximum index used.

Tip
Use the memory option to limit the amount of RAM used to hold the data of the array
when you work with high numbered indexes (and large cells).

If you use all (or nearly all) of the entries in an array, set the memory option to a large
number, or do not set it at all.
If you only use a few of the entries in the array, set the memory option to a small number,
such as 1.

Read Performance
If you consider using the memory option on an array where you use all (or almost all) of the
entries, the look up performance should be considered.
If you traverse an array sequentially, such as with an index of 1, 2, 3, ..., n, you will probably
not experience any read performance problems. The cell data blocks will be read
sequentially from disk and they will be read to the end before the next block is read (disk
reads will be number of entries/memory option size).
If you traverse an array randomly (such as with an index of 300, 20, 5, 250, n, ..., 50) the cell
data will also be read from disk randomly, so you may experience read performance
problems (disk reads could be as high as the number of entries).

Example 1
MyTable myTable;
boolean foundRecord[,1];
;

while select myTable


where myTable
...
{
foundRecord[myTable.recId] = true;
...
}

Example 2
CustTable custTable;
CustAccount foundAccount[];
int i;
;
while select custTable
where custTable
...
{
i++;
foundAccount[i] = custTable.AccountNum;
...
}

Example 3
Name foundName[,100];
int i;
;
while select custTable

where custTable
...
{
i++;
foundName[i] = custTable.Name;
}

X++ Standards: Dates


When you are programming with dates, Best Practices are:

Use only strongly typed (date) fields, variables, and controls (do not use str or int).

Use Auto settings in date formatting properties.

Use DateTimeUtil::getSystemDateTime instead of systemDateGet or today. The today


function uses the date of the machine. The systemDateGet method uses the system
date in Microsoft Dynamics AX. Only DateTimeUtil::getSystemDateTime compensates
for the time zone of the user.

Avoid using date2str for performing date conversions.

Use Strong Typing (date)


Never permanently store a date in anything other than a date field.
Always present a date in a date control.
Fields, variables, and controls should be defined by extended data types. These should have
their formatting properties set to Auto so that you do not take control away from the users'
specific date formatting setup.

Current Business Date


Most application logic should use the system function systemDateGet , which holds the
logic business date of the system (this can be set from the status bar).
The system function today() should be used only where the actual machine date is needed.
This is seldom the case.

Note
The date and time can be different on the client and the server.

Avoid String / Date Conversions


You will not typically need to format a date to a string. Use date fields, variables, and date
controls on forms and reports instead.
If you need to format a date to a string:

For user interface situations, use strFmt or date2Str with -1 in all the formatting
parameters. This ensures that the date is formatted in the way that the user has
specified in Regional Settings.

For other specific system-related situations, such as communication with external


systems, use date2Str.

When you let Regional Settings dictate the format, be aware that it can change from user
to user and might not be a suitable format for external communication.
Using str2Date indicates that dates are being used that have had a string format.

X++ Standards: try/catch Statements


Always create a try/catch deadlock/retry loop around database transactions that might lead
to deadlocks.
Whenever you have a retry, all the transient variables must be set back to the value they
had just before the try. The persistent variables (that is, the database and the Infolog) are
set back automatically by the throw that leads to the catch/retry.

Example
Copy
try
{
this.createJournal();
this.printPosted();
}
catch (Exception::Deadlock)
{
this.removeJournalFromList();
retry;
}

X++ Standards: throw Statements

The throw statement automatically initiates a ttsAbort, which is a database transaction


rollback.
The throw statement should be used only if a piece of code cannot do what it is expected to
do. The throw statement should not be used for more ordinary program flow control.
Always place an explanation of the throw in the Infolog before the actual throw.

Note
Do not use ttsAbort directly; use throw instead.

X++ Standards: ttsBegin and ttsCommit


ttsBegin and ttsCommit must always be used in a clear and well-balanced manner. Balanced
ttsBegin and ttsCommit statements are the following:

Always in the same method.

Always on the same level in the code.

Avoid making only one of them conditional.


Use throw, if a transaction cannot be completed.
Do not use ttsAbort; use throw instead.
Do not use anything that requires a user interaction within a transaction (such as an action
on a dialog box).

X++ Standards: if ... else and switch


Statements
This topic describes X++ code style standards for the if...else statement and the switch
statement.

if...else
If you have an if...else construction, then use positive logic:
Preferred:
if (true)
{

...
}
else
{
...
}
Avoid:
if (!false)
{
...
}
else
{
...
}
It is acceptable to use negative logic if throwing an error, and in cases where the use of
positive logic would make the code difficult to understand.
There should be a space character between the if keyword and the open parenthesis.

Switch Statements
Always end a case with a break statement (or return/throw). If you intentionally want to
make use of the fall-through mechanism supported in X++, replace the missing break
statement with a comment line:
// Fall through
This comment line makes it visually clear to the reader that the fall-through mechanism is
utilized.
Use 3 levels of indentation:
switch (Expression)

{
case: Constant:
Statement;
break;
...
}
Do not put parentheses around cases.
There should not be a space between the case keyword and the colon character.

Use a switch Instead of Nested if ... else


Statements
Use switch statements instead of nested if...else statements.
Recommended:
switch (myEnum)
{
case ABC::A:
...
break;
case ABC::B:
...
break;
case ABC::C:
...
break;
default:
...

break;
}
Avoid:
if (myEnum == ABC::A)
{
...
}
else
{
if (myEnum == ABC::B)
{
...
}
else
{
if (myEnum == ABC::C)
{
...
}
else
{
...
}
}
}

Switch Statements
The switch statement is a multi-branch language construct. You can create more than two
branches by using the switch statement. This is in contrast to the if statement. You have to
nest statements to create the same effect.
The general format of a switch statement is as follows.
switch (Expression)
{
case Constant:
Statement;
break;
...
default:
Statement;
break;
}
The switch expression is evaluated and checked against each of the case compile-time
constants. If a constant matches the switch expression, the case statement is executed. If
the case also contains a break statement, the program then jumps out of the switch. If there
is no break statement, the program continues evaluating the other case statements.
If no matches are found, the default statement is executed. If there are no matches and no
default, none of the statements inside the switch are executed.
Each of the previous Statement lines can be replaced with a block of statements by
enclosing the block in {...} braces.

Syntax
Switch statement = switch( expression) { {case } [ default: statement ] }
case = case expression { , expression } : statement

Examples

switch (Debtor.AccountNo)
{
case "1000" :
do_something;
break;
case "2000" :
do_something_else;
break;
default :
default_statement;
break;
}
It is possible to make the execution drop through case branches by omitting a break
statement. For example:
switch (x)
{
case 10:
a = b;
case 11:
c = d;
break;
case 12:
e = f;
break;
}

Here, if x is 10, b is assigned to a, and d is assigned to c, the break statement is omitted


after the case 10: statement. If x is 11, d is assigned to c. If x is 12, f is assigned to e.

Ternary Operator (?)


In the X++ language of Microsoft Dynamics AX, the ternary operator is a conditional
statement that resolves to one of two expressions. This means that a ternary operation
statement can be assigned to a variable. In comparison, an if statement provides conditional
branching of program flow but cannot be assigned to a variable.

Syntax
expression1 ? expression2 : expression3
expression1 must be a Boolean expression. If expression1 is true, the whole ternary
statement resolves to expression2; otherwise it resolves to expression3.
expression2 and expression3 must be of the same type as each other.

Example 1
This section describes a code example that returns one of two strings based on a Boolean
return value from a method call. The Boolean expression indicates whether the CustTable
table has a row with a RecId field value of 1.
result = (custTable::find("1").RecId) ? "found" : "not found";
If this Boolean expression is true (meaning RecId != 0), found is assigned to result.
Otherwise, the alternative not found is assigned to result.

Example 2
This section describes a code example that has one ternary statement nested inside another
ternary statement.
Copy
print( (custTable.AccountNum > "1000")
? ( (custTable.AccountNum < "2000")
? "In interval" : "Above 2000"
)
: "low"
);
If AccountNum is not greater than 1000, the expression is equal to the third expression and
low is printed.

If AccountNum is greater than 1000, the second expression is evaluated, and this also
contains a ternary operator. If AccountNum is greater than 1000 and less than 2000, In
interval is printed. If AccountNum is greater than 1000 and greater than or equal to 2000,
Above 2000 is printed.

Comparison to the IF Statement


This section describes a comparison of the ternary statement to the if statement.
Copy
a = (b > c) ? b : c;
// The preceding line of code is equivalent to the following if-else code:
if (b > c)
{
a = b;
}
else
{
a = c;
}

Transaction Integrity
Microsoft Dynamics AX has two internal checking features to help ensure the integrity of
transactions made by X++ programmers.
If the integrity of transactions is not ensured, it may lead to data corruption, or, at best, poor
scalability with reference to concurrent users on the system.

forUpdate Checking
This check ensures that no record can be updated or deleted if the record has not first been
selected for update. A record can be selected for update, either by using the forUpdate
keyword in the select statement, or by using the selectForUpdate method on tables.

ttsLevel Checking
This check ensures that no record can be updated or deleted except from within the same
transaction scope as it was selected for update. Integrity is ensured by using the following
statements:

ttsBegin: marks the beginning of a transaction. This ensures data integrity, and
guarantees that all updates performed until the transaction ends (by ttsCommit or
ttsAbort) are consistent (all or none).

ttsCommit: marks the successful end of a transaction. This ends and commits a
transaction. MorphX guarantees that a committed transaction will be performed
according to intentions.

ttsAbort: allows you to explicitly discard all changes in the current transaction. As a
result, the database is rolled back to the initial statenothing will have been
changed. Typically, you will use this if you have detected that the user wants to break
the current job. Using ttsAbort ensures that the database is consistent.

Note
It is usually better to use exception handling instead of ttsAbort. The throw statement
automatically aborts the current transaction.

Statements between ttsBegin and ttsCommit may include one or more transaction blocks as
shown in the following example.
ttsBegin;
// Some statements.
ttsBegin;
// Statements.
ttsCommit;
ttsCommit;
In such cases, you should note that nothing is actually committed until the successful exit
from the final ttsCommit.

Examples
Example use of ttsBegin and ttsCommit
Copy
Custtable custTable;
;
ttsBegin;
select forUpdate custTable where custTable.AccountNum == '4000';
custTable.NameAlias = custTable.Name;
custTable.update();
ttsCommit;

Examples of Code Rejected by the two Transaction Integrity Checks


Copy
ttsBegin;
select myTable; // Rejected by the forUpdate check.
mytable.myField = 'xyz';
myTable.update();
ttsCommit;
ttsBegin;
select forUpdate * from myTable;
myTable.myField = 'xyz';
ttsCommit;
...
ttsBegin;
myTable.update(); // Rejected by the ttsLevel check.
ttsCommit;
The first failure is because the forupdate keyword is missing.
The second failure is because the update is in another transaction scope rather than the one
the that record was selected in ttsCommit for update.

Exception Handling
You can write your X++ code to handle errors by using the statements for generating and
handling exceptions.
For example, your method might receive an input parameter value that is invalid. Your
method can throw an exception to immediately transfer control to a catch code block that
contains logic to handle this particular error situation. You do not necessarily need to know
the location of the catch block that will receive control when the exception is thrown.

What is an Exception?
An exception is a regulated jump away from the regular sequence of program instruction
execution. The instruction at which program execution resumes is determined by try - catch
blocks and the type of exception that is thrown.
In X++, an exception is represented by a value of the enum named Exception. A frequently
thrown exception is Exception::error enumeration value. This exception is thrown in a variety
of situations. It is common practice to write diagnostic information to the Infolog before
throwing the exception, and the Global::error method is often the best way to do that.

Exception Related X++ Statements


You use the following X++ statements to generate and handle exceptions:

throw

try

catch

retry

Note
There is no finally statement in X++.

The throw Statement with an Exception Member


You can use the throw keyword to throw an Exception enum value. For example, the
following statement throws an enum value as an exception:
throw Exception::error;

The throw Statement with the Global::error Method


Instead of throwing an enum value, it is a best practice to use the Global::error method
output as the operand for throw:
throw Global::error("The parameter value is invalid.");
The Global::error method can automatically convert a label into the corresponding text. This
helps you to write code that can be more easily localized.
throw Global::error("@SYS98765");

Tip
In X++ code, the static methods on the Global class can be called without the Global::
prefix. For example, the Global::error method can be called simply as error("My
message.");.

The throw Statement without an Operand

Inside a catch block, you can write the throw; statement without specifying anything else in
the statement. This re-throws the same exception value that the catch block caught. You
might re-throw an exception when your method has no other safe way to continue.

The try and catch Statements


When an exception is thrown, it is first processed through the catch list of the innermost try
block.
If a catch is found that handles the kind of exception that is being thrown, program control
jumps to that catch block. If the catch list has no block that specifies the particular
exception, the system passes the exception to the catch list of the next innermost try block.
The catch statements are processed in the same sequence that they appear in the X++
code. It is common to have the first catch statement handle the Exception::Error
enumeration value.
One strategy is to have the last catch statement leave the exception type unspecified. This
means it handles all exceptions that are not handled by a previous catch. This strategy is
appropriate for the outermost try - catch blocks.
try { /* Code here. */ }
catch (Exception::Numeric) { info("Caught a Numeric exception."); }
catch { info("Caught an exception."); }

The retry Statement


The retry statement can be written only in a catch block. The retry statement causes control
to jump up to the first line of code in the associated try block.

Caution
You must prevent your use of retry from causing an infinite loop. The early statements in
your try block must contain an if test of a variable that eventually ends the looping.

The retry statement is used when the cause of the exception can be fixed by the code in the
catch block. The retry statement gives the code in the try block another chance to succeed.

Note
The retry statement erases messages that were written to the Infolog since program
control entered the try block.

The System Exception Handler


If no catch statement handles the exception, it is handled by the system exception handler.
The system exception handler does not write to the Infolog. This means that an unhandled
exception can be hard to diagnose. Therefore we recommended that you do all the following
to provide effective exception handling:

Have a try block that contains all your statements in the outermost frame on the call
stack.

Have an unqualified catch block at the end of your outermost catch list.

Avoid throwing an Exception enum value directly.

Do throw the enum value that is returned from one of the following methods on the
Global class (you have the option of omitting the implicit Global:: prefix):

Global::error

Global::warning

Global::info

When you catch an exception that has not been displayed in the Infolog, call the
Global::info function to display it.

Tip
Exception::CLRError, Exception::UpdateConflictNotRecovered, and system kernel
exceptions are examples of exceptions that are not automatically displayed in the
Infolog.

Exceptions and CLR Interop


From X++ you can call .NET Framework classes and methods that reside in assemblies that
are managed by the common language runtime (CLR). When a .NET Framework
System.Exception instance is thrown, your code can catch it by referencing
Exception::CLRError.

Your code can obtain a reference to the System.Exception instance by calling the
CLRInterop::getLastException method.

Ensure Exceptions are Displayed


Exceptions of type Exception::CLRError are not displayed in the Infolog. These exceptions
are not issued by a call to a method such as Global::error. In your catch block, your code can
call Global::error to report the specific exception.

Global Class Methods for Exceptions


This section describes some Global class methods in more detail.

The Global::error Method Parameters


The error method is declared as follows:
Copy
server client static Exception error
(SysInfoLogStr txt,
URL helpURL = '',
SysInfoAction _sysInfoAction = null)
The return type is the Exception::Error enum value. The error method does not throw an
exception. It only provides an enum value that could be used in a throw statement. The
throw statement throws the exception.
Only the first parameter is required. The parameters are described in the following table.

Parameter

Description

SysInfoLogStrtxt

A str of the message text.


This can also be a label reference, such as strFmt("@SYS12345",
strThingName).

URLhelpUrl

A reference to the location of a Help topic in the Application


Object Tree (AOT). For example:

"KernDoc:\\\\Functions\\substr"
This parameter value is ignored if _sysInfoAction is supplied.
SysInfoAction_sysInfoAc An instance of a class that extends the SysInfoAction class.
tion
The following list shows the method overrides we recommend for
the child class:

description

run

pack

unpack

For sample code that uses SysInfoAction, see Sample 6.

The Global::info Method


The Global::info method is routinely used to display text in the Infolog. It is often written in
programs as just info("My message.");. Even though the info method returns an
Exception::Info enumeration value it would be rare to want to throw an Exception::Info
because nothing unexpected has occurred.

The Global::exceptionTextFallThrough Method


Occasionally you want to do nothing inside your catch block. The X++ compiler issues a
warning when you have an empty catch block. You should avoid this warning by calling the
Global::exceptionTextFallThrough method in the catch block. The method does nothing, but it
satisfies the compiler.

Exceptions Inside Transactions


If an exception is thrown inside a transaction, the transaction is automatically aborted (a
ttsAbort operation occurs). This applies both for exceptions thrown manually and for
exceptions thrown by the system.
When an exception is thrown inside a ttsBegin - ttsCommit transaction block, no catch
statement inside that transaction block can process the exception. Instead, the innermost

catch statements that are outside the transaction block are the first catch statements to be
tested.

Code Samples
The next sections have the following code samples:

Sample 1: Display exceptions in the infolog

Sample 2:error method to write exception to infolog

Sample 3: Handle a CLRError

Sample 4: Use of the retry statement

Sample 5: Exception thrown inside a transaction

Sample 6: throw Global::error with a SysInfoAction parameter

Sample 1: Display Exceptions in the Infolog


This X++ code sample shows that a direct throw of Exception::Error does not display a
message in the Infolog. That is why we recommend the Global::error method.
Copy
static void TryCatchThrowError1Job(Args _args)
{
/***
The 'throw' does not directly add a message to the Infolog.
The exception is caught.
***/
;
try
{
info("In the 'try' block. (j1)");

throw Exception::Error;
}
catch (Exception::Error)
{
info("Caught 'Exception::Error'.");
}
/********** Actual Infolog output
Message (03:43:45 pm)
In the 'try' block. (j1)
Caught 'Exception::Error'.
**********/
}

Sample 2: error Method to Write Exception to


Infolog
The sample shows that use of the Global::error method is a reliable way to display
exceptions in the Infolog.
Copy
static void TryCatchGlobalError2Job(Args _args)
{
/***
The 'Global::error()' does directly add a message to the Infolog.
The exception is caught.
***/
;
try
{
info("In the 'try' block. (j2)");

throw Global::error("Written to the Infolog.");


}
catch (Exception::Error)
{
info("Caught 'Exception::Error'.");
}
/********** Actual Infolog output
Message (03:51:44 pm)
In the 'try' block. (j2)
Written to the Infolog.
Caught 'Exception::Error'.
**********/
}

Sample 3: Handle a CLRError


This sample shows that a CLRError exception is not displayed in the Infolog (unless you
catch the exception and manually call the info method). The use of the
CLRInterop::getLastException method is also demonstrated.
Copy
static void TryCatchCauseCLRError3Job(Args _args)
{
/***
The 'netString.Substring(-2)' causes a CLRError,
but it does not directly add a message to the Infolog.
The exception is caught.
***/
System.String netString = "Net string.";
System.Exception netExcepn;

;
try
{
info("In the 'try' block. (j3)");
netString.Substring(-2); // Causes CLR Exception.
}
catch (Exception::Error)
{
info("Caught 'Exception::Error'.");
}
catch (Exception::CLRError)
{
info("Caught 'Exception::CLRError'.");
netExcepn = CLRInterop::getLastException();
info(netExcepn.ToString());
}
/********** Actual Infolog output (truncated for display)
Message (03:55:10 pm)
In the 'try' block. (j3)
Caught 'Exception::CLRError'.
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an
invocation. ---> System.ArgumentOutOfRangeException: StartIndex cannot be less than
zero.
Parameter name: startIndex
at System.String.InternalSubStringWithChecks(Int32 startIndex, Int32 length, Boolean
fAlwaysCopy)
at System.String.Substring(Int32 startIndex)

at ClrBridgeImpl.InvokeClrInstanceMethod(ClrBridgeImpl* , ObjectWrapper* objectWrapper,


Char* pszMethodName, Int32 argsLength, ObjectWrapper** arguments, Boolean*
argsAreByRef, Boolean* isException)
**********/
}
For more information, see How to: Catch Exceptions Thrown from CLR Objects.

Sample 4: Use of the retry Statement


This sample shows how to use the retry statement. The print statements are included
because retry causes earlier Infolog messages to be erased.
Copy
static void TryCatchRetry4Job(Args _args)
{
/***
Demonstration of 'retry'. The Infolog output is partially erased
by 'retry', but the Print window is fully displayed.
***/
Exception excepnEnum;
int nCounter = 0;
;
try
{
info("
print("

.");
.");

info("In the 'try' block, [" + int2str(nCounter) + "]. (j4)");


print("In the 'try' block, [" + int2str(nCounter) + "]. (j4)");
pause;
nCounter++;

if (nCounter >= 3) // Prevent infinite loop.


{
info("---- Will now throw a warning, which is not caught.");
print("---- Will now throw a warning, which is not caught.");
pause;
throw Global::warning("This warning will not be caught. [" + int2str(nCounter) + "]");
}
else
{
info("Did not throw a warning this loop. [" + int2str(nCounter) + "]");
print("Did not throw a warning this loop. [" + int2str(nCounter) + "]");
}
excepnEnum = Global::error("This error message is written to the Infolog.");
throw excepnEnum;
}
catch (Exception::Error)
{
info("Caught 'Exception::Error'.");
print("Caught 'Exception::Error'.");
retry;
}
info("End of job.");
print("End of job.");
pause;
/********** Actual Infolog output
Message (04:33:56 pm)
.
In the 'try' block, [2]. (j4)

---- Will now throw a warning, which is not caught.


This warning will not be caught. [3]
**********/
}

Sample 5: Exception Thrown Inside a Transaction


This sample uses three levels of try nesting to illustrate where an exception is caught when
the exception is thrown inside a ttsBegin - ttsCommit transaction block.
Copy
static void TryCatchTransaction5Job(Args _args)
{
/***
Shows an exception that is thrown inside a ttsBegin - ttsCommit
transaction block cannot be caught inside that block.
***/
;
try
{
try
{
ttsbegin;
try
{
throw error("Throwing exception inside transaction.");
}
catch (Exception::Error)
{
info("Catch_1: Unexpected, caught in 'catch' inside the transaction block.");

}
ttscommit;
}
catch (Exception::Error)
{
info("Catch_2: Expected, caught in the innermost 'catch' that is outside of the
transaction block.");
}
}
catch (Exception::Error)
{
info("Catch_3: Unexpected, caught in 'catch' far outside the transaction block.");
}
info("End of job.");
/********** Actual Infolog output
Message (04:12:34 pm)
Throwing exception inside transaction.
Catch_2: Expected, caught in the innermost 'catch' that is outside of the transaction block.
End of job.
**********/
}

Sample 6: use Global::error with a SysInfoAction


parameter
When your code throws an exception, your code can write messages to the Infolog window.
You can make those Infolog messages more helpful by using the SysInfoAction class.
In the following X++ code sample, a SysInfoAction parameter is passed in to the
Global::error method. The error method writes the message to the Infolog. When the user

double-clicks the Infolog message, the SysInfoAction.run method is run. You can write code
in the run method that helps to diagnose or fix the problem that caused the exception.
The object that is passed in to the Global::error method is constructed from a class that you
write that extends SysInfoAction.
The following code sample is shown in two parts. The first part shows a job that calls the
Global::error method, and then throws the returned value. An instance of the
SysInfoAction_PrintWindow_Demo class is passed into the error method. The second part
shows the SysInfoAction_PrintWindow_Demo class.

Part 1: The Job that calls Global::error


Copy
static void Job_SysInfoAction(Args _args)
{
;
try
{
throw Global::error
("Click me to make the Print window display."
,""
,new SysInfoAction_PrintWindow_Demo()
);
}
catch
{
warning("Issuing a warning from the catch block.");
}
}

Part 2: the SysInfoAction_PrintWindow_Demo class


Copy
public class SysInfoAction_PrintWindow_Demo
extends SysInfoAction

{
str m_sGreeting; // In classDeclaration.

public str description()


{
;
return "Starts the Print Window for demonstration.";
}

public void run()


{
;
print("This appears in the Print window.");
print(m_sGreeting);
pause;

/*********** Actual Infolog output


Message (03:19:28 pm)
Click me to make the Print window display.
Issuing a warning from the catch block.
***************/
}

public container pack()


{
;

return ["Packed greeting."]; // Literal container.


}

public boolean unpack


(container packedClass
, Object object = null
)
{
;
[m_sGreeting] = packedClass;
return true;
}
}

List of Exceptions
The exception literals shown in the following table are the values of the Exception System
Enumeration.

Exception literal

Description

Break

Indicates that the user has pressed BREAK or CTRL+C.

CLRError

Indicates that an error has occurred during the use of the


common language runtime (CLR) functionality.

CodeAccessSecurity

Indicates that an error has occurred during the use of the


CodeAccessPermission.demand method. For more
information, see Code Access Security.

DDEerror

Indicates that an error occurred in the use of the DDE


system class.

Deadlock

Indicates that there is a database deadlock because


several transactions are waiting for each other.

DuplicateKeyException

Indicates that an error has occurred in a transaction that


is using Optimistic Concurrency Control. The transaction
can be retried (use a retry statement in the catch block).

DuplicateKeyExceptionNotReco

Indicates that an error has occurred in a transaction that

vered

is using Optimistic Concurrency Control. The code will not


be retried.
Note
This exception cannot be caught inside a transaction.

Error

Indicates that a fatal error has occurred. The transaction


has been stopped.

Info

Holds a message for the user.


Do not throw an info exception.

Internal

Indicates an internal error in the development system.

Numeric

Indicates that an error has occurred during the use of the


str2int, str2int64, or str2num functions.

Sequence

(TBD)

UpdateConflict

Indicates that an error has occurred in a transaction that


is using Optimistic Concurrency Control. The transaction
can be retried (use a retry statement in the catch block).

UpdateConflictNotRecovered

Indicates that an error has occurred in a transaction that


is using Optimistic Concurrency Control. The code will not
be retried.
Note
This exception cannot be caught within a transaction.

Warning

Indicates that something exceptional has happened. The


user might have to take action, but the event is not fatal.
Do not throw a warning exception.

Exception Handling for User Controls


Code you add to User Controls may call methods that throw exceptions. It is important that
your code correctly handle any exceptions encountered. Unhandled exceptions create a poor
experience for the user. The Enterprise Portal framework can help you manage exceptions.

Exception Categories
Exceptions in Enterprise Portal are divided into three categories. These exception categories
are defined in the enumeration AxExceptionCategory. They are described in the following
table.

Exception
Category

Description

NonFatal

Indicates an exception that was expected. The exception handling code


should respond appropriately and allow for the request to continue
normally.

AxFatal

Indicates that an unrecoverable error has occurred in Enterprise Portal.


Enterprise Portal content will not display. Non-portal content should display
as expected.

SystemFatal

Indicates a serious error has occurred and the request must be aborted.
Errors of this kind often cause an HTTP error code 500.

When Exception Handling Is Needed


You must handle exceptions when code you add to a User Control directly calls code that can
throw exceptions. The following are common situations where this occurs.

Your code directly calls methods from the Enterprise Portal framework that can throw
exceptions. For instance, the EndEdit method for a DataSetViewRow object can
encounter exceptions as a result of the edit operation. If your code called this method
directly, it must handle the exceptions.

Your code calls X++ methods through the proxy, and the X++ methods throw
exceptions. Your code must handle the exceptions.

The controls for Enterprise Portal have built-in functionality to perform standard actions such
as editing a row in a grid or saving the contents of a form. The code for this built-in
functionality properly handles exceptions. If the action started with the built-in functionality
of a control, such as a user clicking the OK button for an AxForm, you do not need to add
code to handle exceptions for that action.

Exception Handling Code


The code to handle exceptions for User Controls in Enterprise Portal has the following basic
form. This code calls the TryHandleException method to determine the category of the
Enterprise Portal exception. Based on the category, the code will try to correctly respond to
the exception.
Copy
try
{
// Code that may encounter exceptions goes here.
}
catch (System.Exception ex)
{
AxExceptionCategory exceptionCategory;
// Determine whether the exception can be handled.
if (AxControlExceptionHandler.TryHandleException(this, ex, out exceptionCategory) ==
false)
{
// The exception was fatal and cannot be handled. Rethrow it.
throw;
}

if (exceptionCategory == AxExceptionCategory.NonFatal)
{
// Application code to properly respond to the exception goes here.
}

Note
The Enterprise Portal framework will automatically display the exception message in the
Infolog Web part on the page.

The code that runs in response to the exceptions should leave Enterprise Portal in a valid
state. For example, if a validation exception occurs when the user clicks the OK button to
save the values on a page, data will not be saved. The code in the exception handler should
prevent Enterprise Portal from redirecting to a different page. This allows for the user to see
the error on the page, correct it, and then perform the action again.

Exception Example
The CalculateGrade method is an X++ method that is part of the Test class added to
Microsoft Dynamics AX. The method takes a numeric grade value and returns the
corresponding letter grade. If the input value is outside the valid range (0 to 100), an
exception is thrown. The proxy was used to make this method available to User Controls.
The following example is the click method for a button added to a User Control. It calls the
CalculateGrade method to retrieve a letter grade based on a test score. The code handles
the exception that occurs if the input value is out of range. In this case, it displays text that
indicates the grade is out of range.
Copy
protected void Button1_Click(object sender, EventArgs e)
{
string letterGrade;
// Method can throw an exception, so an exception handler is used.
try
{
letterGrade = Test.CalculateGrade(this.AxSession.AxaptaAdapter,
Convert.ToInt16(TextBoxScore.Text));
TextBoxGrade.Text = letterGrade;
}
catch (System.Exception ex)
{
AxExceptionCategory exceptionCategory;
if(AxControlExceptionHandler.TryHandleException(this, ex, out exceptionCategory) ==
false)
{
// The exception was fatal, so rethrow it.
throw;
}

if(exceptionCategory == AxExceptionCategory.NonFatal)
{
if (ex.InnerException.Message.Equals("Grade is out of range"))
{
TextBoxGrade.Text = "<<Grade out of range>>";
}
}

insert Table Method

The xRecord.insert method generates values for RecId and system fields, and then inserts
the contents of the buffer into the database.
The method operated as follows:

Only the specified columns of those rows selected by the query are inserted into the
named table.

The columns of the table being copied from and those of the table being copied to
must be type compatible.

If the columns of both tables match in type and order, the column-list may be omitted
from the insert clause.

The insert method updates one record at a time. To insert multiple records at a time, use
array inserts, insert_recordset, or RecordSortedList.insertDatabase.
To override the behavior of the insert method, use the doInsert method.

Example 1
The following code example inserts a new record into the CustTable table, with the
AccountNum set to 5000 and the Name set to MyCompany (other fields in the record will be
blank).
Copy
CustTable custTable;
;
ttsBegin;
select forUpdate custTable;
custTable.AccountNum = '5000';
custTable.Name = 'MyCompany';
custTable.insert();
ttsCommit;

Example 2: Transaction and Duplicate Key


The following example shows how you can catch a DuplicateKeyException in the context of
an explicit transaction. The exception is thrown when a call to xRecord.insert fails because of
a duplication of an existing unique value. In the catch block, your code can take corrective
action, or it can log the error for later analysis. Then your code can continue without losing
all the pending work of the transaction.

Note
You cannot catch a duplicate key exception caused by a set based operation such as

insert_recordset.

This example depends on two tables TableNumberA and TableNumberB. Each has one
mandatory Integer field, named NumberAKey and NumberBKey respectively. Each of these
key fields has a unique indexed defined on it. The TableNumberA table must have at least
one record in it.
static void JobDuplicKeyException44Job(Args _args)
{
TableNumberA tabNumA; // Has one record, key = 11.
TableNumberB tabNumB;
AddressState tabAddressState;
int iCountTries = 0
,iNumberAdjust = 0
,iNewKey
,ii;
container ctNotes;
;
// Empty the B table.
delete_from tabNumB;
// Insert a copy of one record.
insert_recordset tabNumB (NumberBKey)
select firstOnly NumberAKey from tabNumA
order by NumberAKey asc;
ttsBegin;
try
{
iCountTries++;

ctNotes += strFmt
("---- Inside the try block, try count is %1. ----"
,iCountTries);
while select * from tabNumA
order by NumberAKey asc
{
tabNumB .clear();
iNewKey = tabNumA .NumberAKey + iNumberAdjust;
tabNumB .NumberBKey = iNewKey;
ctNotes += strFmt
("-- %1 is the key to be tried. --" ,iNewKey);
tabNumB .insert();
ctNotes += "-- .insert() successful. --";
break; // Keeps demo simple.
}
ttsCommit;
}
catch (Exception ::DuplicateKeyException
,tabNumB) // Table is optional.
{
ctNotes += "---- Inside the catch block. ----";
ctNotes += infolog .text();
if (iCountTries <= 1)
{
ctNotes += "-- Will issue retry. --";

iNumberAdjust = 1;
retry; // Erases Infolog.
}
else
{
ctNotes += "-- Aborting the transaction. --";
ttsAbort;
}
}
for (ii=1; ii <= conLen(ctNotes); ii++)
{
info(conPeek(ctNotes ,ii));
}
/*********** Actual output
Message (10:53:13 am)
---- Inside the try block, try count is 1. ----- 11 is the key to be tried. ----- Inside the catch block. ---Cannot create a record in TableNumberB (TableNumberB).
The record already exists.
-- Will issue retry. ----- Inside the try block, try count is 2. ----- 12 is the key to be tried. --- .insert() successful. -***********/

Updating, Deleting, and Inserting Data


This section of the SDK provides information about the table methods provided for updating,
inserting, and deleting data.
update Table Method
doUpdate Table Method
delete Table Method
doDelete Table Method
insert Table Method
doInsert Table Method

Swapping Arrays to Disk


When you declare an array, one of your options is to specify how many array items are to be
held in memory. The remaining array items are swapped to disk.

Note
Use this option with caution, as it could lead to excessive disk swapping or excessive
memory usage.

A dynamic array is sized according to the largest index ever used in the array. For example,
if you use an index of 1000 in an array, the size of the array is set to 1000. If you then use
an index of 500, the array size remains 1000.
The general rules are:

If you use all or nearly all entries in an array, set the memory option to a large
number or do not set the option at all.

If you use few, non-consecutive entries in the array, set the memory option to a small
number, such as 1.

If you use record IDs as indexes, set the memory option to 1. Record IDs are typically
very large integers. When you use them as indexes, the size of your dynamic arrays
grows unacceptably large.
For example:

MyTable myTable;
boolean foundRecord[,1];
;
while select myTable
where myTable ...
{
foundRecord[myTable.RecId] = true;
...
}

Declaration of Variables
All variables must be declared before they can be used. X++ does not allow variable
declarations to be mixed with other X++ statements; variables must be declared before X+
+ statements.
You should separate variable declarations from the rest of your code with a semi-colon (;) on
a separate line, at the end of the declarations:
int i;
;
// Other code
The syntax for the declaration of each variable type is described in the help topics for the
Primitive Data Types and Composite Data Types.
When a variable is declared, memory is also allocated and the variable is initialized to the
default value. The only exception to this is for objects, where you need to manually allocate
memory by using the new method. For more information, see Classes as Data Types.

Declaration With Initialization


At times you might want a variable to have a value other than the default as soon as the
variable is declared. X++ allows you to initialize variables in the declaration by adding an
assignment statement:
// Assigns value of pi to 12 significant digits

real pi = 3.14159265359;
Another syntax is needed to initialize objects because they are initialized by invoking the
new method on the class:
// Simple call to the new method in the Access class
Access accessObject = new Access();

Multiple Declarations
X++ allows you to declare more than one variable in the same declaration statement. For
example:
// Declares 2 integers, i and j
int i,j;
// Declares array with 100 integers with 5 in memory and b as integer with value 1
int a[100,5], b=1;

Summary
The possible variable declarations in X++ are shown by using EBNF (Extended Backus Naur
Form) grammar, in the following table:

Declaration

DatatypeVariable{ ,Variable} ;

Datatype

boolean | int | real | date | str | container | typeidentifier

Variable

[ Typeoptions ] Identifier [ Option ]

Typeoptions

[ Length ] [ left | right ]

Option

Arrayoption | Initialization

Arrayoption

[ Length , Memory ]

Initialization

expression

typeIdentifier can be a class, an extended data type, a table, a map, or an enumeration,


which has been declared elsewhere. All of these are declared in the Application Object Tree.
Typeoptions are only valid for variables of type string.
Length is an integer (or expression) that specifies the maximum length of the string or array.
If it is omitted, the string or array is dynamic.
Memory is an integer (or expression) that specifies how many array items should be held in
memory. If it omitted, all items are held in memory.

Composite Data Types


The composite data types in X++ are listed in the following table. For more information
about each data type, click its link.

X++ composite
data types

Description

Arrays

An array is a list of items with the same data type and the same
nameonly the index differs.

Containers

A container is a dynamic list of items containing primitive data


types and/or some composite data types.

Classes as Data

A class is a type definition that describes both variables and

Types

methods for instances (objects) of the class.

Tables as Data Types

All tables defined in the database can be handled as class


definitions.

The Collection classes allow you to create arrays, lists, sets, maps, and structs that can hold
any data type, including objects.

Default Values on Composite Types


The default values for variables of composite data types are shown in the following table.

Data
type

Default Description

Array

as data

The default value on an array is that all items have their normal

type

default value.

Containe empty

The default value of a container is empty. The container does not

contain any values.

Class

null

A class is only a definition for objects, and all objects are null when
they are declared.

Table

empty

A table-variable can be seen as an instance (an object) of the table


(class) definition. All fields in a table-variable are empty by default.

Extended Data Types


Extended data types are user-defined types, based on the primitive data types Boolean,
integer, real, string, and date, and the composite type container. You can also base
extended data types on other extended data types.
This feature is not implemented as a language construct. Extended data types are defined in
the Application Object Tree (AOT).
An extended data type is a primitive data type or container with a supplementary name and
some additional properties. For example, you could create a new type called Name on the
basis of string and thereafter use the new type in variable and field declarations in the
development environment.
The advantages of extended data types are as follows:

Code is easier to read because variables have a meaningful data type. For example,
Name instead of string.

The properties you set for an extended data type are used by all instances of that
type, which reduces work and promotes consistency. For example, account numbers
(AccountNum data type) have the same properties throughout the system.

You can create hierarchies of extended data types, inheriting the properties that are
appropriate from the parent and changing the other properties. For example, the
ItemCode data type is used as the basis for the MarkupItemCode and
PriceDiscItemCode data types.

Range and Precision


The range of an extended data type is identical to that of the base type it is based on.

Declaration of Extended Data Type Variables


The Extended Data Types node below the Data Dictionary node in the AOT is used to create
extended data types. When declaring variables in X++, use the syntax shown in the
following table.

Extended declaration

ExtendedtypeVariable{ ,Variable} ;

Variable

Identifier [ option ]

Option

arrayoptions | initialization

where Extendedtype is the name of the extended data type in the AOT.
// A UserGroupID (integer) variable is declared and initialized to 1
UserGroupID groupID = 1;
// An Amount (real) variable is declared
Amount currency;

Automatic Conversion

Because extended data types are normal data types but with a specific name and
properties, all standard conversions are performed.

S-ar putea să vă placă și