You are on page 1of 453

Covers DAX 5 and DAX 6

T A S K Kumar | www.axaptaschool.com
DAX
SOFTRONICS
(INDIA) PVT.
LTD.
DAX HAND BOOK
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 2
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 3
DAX
Softronics
(India) Pvt. Ltd.
DAX Hand Book
Covers AX 2009 and AX 2012
T A S K Kumar
www.axaptaschool.com
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 4
DAX Hand Book
Copyright 2013 DAX Softronics (India) Pvt. Ltd.
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any
form or by any means, without the prior written permission of the publisher, except in the case of brief quotations
embedded in critical articles or reviews which are done in consent of author or publishers.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented.
However, the information contained in this book is sold without warranty, either express or implied. Neither the
author, nor DAX Softronics, and its dealers and distributors will be held liable for any damages caused or alleged to
be caused directly or indirectly by this book.
DAX Softronics has endeavored to provide trademark information about all of the companies and products
mentioned in this book by the appropriate use of capitals. However, DAX Softronics cannot guarantee the accuracy
of this information.
First edition: March 2013
Product Id: AX6DHB
ISBN: 978-162840425-8
Product Reference: 1270426
Published by DAX Softronics (India) Pvt. Ltd. Andhra Pradesh, India.
www.daxsoftronics.com
www.axaptaschool.com
Cover Image is created using official logo of Axapta School DAX Softronics (India) Pvt. Ltd. and is used by DAX
Softronics and Axapta School as part of promoting the Microsoft Dynamics AX technology and the copies of this
book are distributed for free to promote the technology. All the images, logos and some other tabular content
which are not relevant to Axapta School and DAX Softronics are taken from respective owners and are not used for
commercial use. Axapta School and DAX Softronics will not encourage piracy in any form and request the readers
to let us know if the material of this book is copied into any format without proper consent of Author and/or DAX
Softronics Pvt Ltd.
Any judicial or legal issues will be followed in Courts of Hyderabad only as per the Indian Penal Code and is subject
to Indian Laws.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 5
Thanks to my Mom, Dad and Bro who supported me a lot while I was
working on this book. Specially my dad whom I love the most in
universe.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 6
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 7
Acknowledgements
Thanks Mohan G Rao my guru, who pushed, helped and supported
while working on this book.
Surya, my favorite lead and who taught me.
Special thanks to my guru Sree who taught me Axapta in my early
stages.
Eshwar, my favorite student guru who made me learn many things and
all learners of this book.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 8
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 9
Contents at a Glance
Introduction
Unit I
1. Architectural Overview
2. ERP and AX
3. MorphX Environment
Unit II
4. X++ Introduction
5. Object Oriented Programming in X++
6. Data Dictionary
7. Macros
8. Queries
9. Resources
Unit III
10. Forms
11. Reports
12. Menus and Menu Items
13. Files
14. Frameworks
15. Miscellaneous
Appendixes
16. New improvements and CIL in AX 2012
17. Installation and configuration
18. Using Debugger in AX
19. Label Wizard and Label Editor
20. Basic Administration
21. Interesting tools in AX Development environment
22. Best practices and their requirement
23. Tips while working in AX development environment.
Links
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 10
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 11
Table of Contents
Introduction
Unit I
1. Architectural Overview 21
a. 3-Tier Architecture 21
b. Layered Architecture and Model Store 22
c. Application Development and Run Time Environments 25
d. Application Framework 26
2. ERP and AX 27
a. What is ERP 27
b. AX and ERP 29
c. Modules 30
3. MorphX Environment 33
a. Application Object Tree 33
Unit II
4. X++ Introduction 41
a. Programming in Microsoft Dynamics AX 41
b. Jobs 44
c. Variables and Types 46
d. Operators 49
e. Control Flow Statements 50
f. Built-in functions 64
g. Basic Input and Output 65
5. Object Oriented Programming in X++ 73
a. Classes 75
i. Creating Classes 76
ii. Variables/Methods/Constructors/Destructors/Access Modifiers 78
iii. Encapsulation 85
iv. Inheritance 85
v. Interfaces and Miscellaneous Concepts 102
vi. Polymorphism 104
vii. Eventing 108
viii. Creating and using Attributes 110
ix. Importance of Classes in DAX 111
b. Types of Classes 111
i. System Classes 111
ii. Application Classes 111
c. Exception Handling 112
d. Other Fundamental Classes [Collections] 114
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 12
6. Data Dictionary 123
a. Extended Data Types 123
i. What are EDTs 123
ii. Power of EDTs in DAX 124
iii. EDT Relations 126
iv. Array Elements in EDT 126
b. Base Enums 127
i. What are Base Enums 127
ii. How to create Base Enums 127
c. Tables 128
i. Basic Persistent Store 128
ii. System Tables and Application Tables 128
iii. Creating Tables/Fields/Various Data Types and Usage 129
iv. Using EDTs and Base Enums 129
v. How the tables in DAX store data 132
vi. Basic Table Properties and Field Properties 134
vii. Field Groups and Requirement 134
viii. Table Indexing/Primary Index/Pros and Cons of Indexing 135
ix. Surrogate Keys 137
x. Table Relations and Types of Table Relations 138
xi. Difference Between EDT Relations and Table Relations 138
xii. Delete Actions/How to play with Cascade + Restricted 143
xiii. Methods as Events in Table/Alternative to PL/SQL Triggers 150
xiv. Transaction Tracking System 161
xv. New Features and Differences in DAX 6 compared to DAX 5 163
a) Table Inheritance 163
d. Maps 168
i. What are Maps 169
ii. Creating and Using Maps 170
iii. Where and Where not to use Maps 173
e. Views 173
i. What are Views 174
ii. Requirement of Views 174
iii. Creating Simple and Complex Views 174
iv. Methods in Views 175
f. License Codes 176
g. Configuration Keys 176
i. What are Configuration Keys 177
ii. Usage and Applying Configuration Keys in Practical 178
h. Security Keys 180
i. Requirement of Security Keys 180
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 13
ii. Applying Security Keys in DAX 181
i. Table Collections 181
i. What are Table Collections 181
ii. Why Table Collections 182
iii. Using Virtual Companies and Table Collections 182
7. Macros 187
a. Macro Commands 187
b. Constants 188
c. Create Macros 188
d. Passing Values 190
8. Queries 195
a. What are Queries and Requirement of Queries 195
b. Inline Queries 195
i. What are Inline Queries 195
ii. Using loops 196
1. Why Inline Queries in spite of Other Queries available 202
c. AOT Queries 209
i. Creating AOT Queries 211
ii. Using AOT Queries in X++ 212
iii. Child Data Sources/Ranges/Sorting and Ranges 214
iv. Data Source Relations 216
v. Data Sources and the properties of Data Sources 217
vi. Composite Queries 221
vii. Why AOT Queries and What is the use of AOT Queries 222
viii. Methods in Queries 222
d. X++ Queries 223
i. Creating and Using X++ Queries 223
ii. Data Sources/Ranges/Child Data Sources/Relations 225
e. When and Where to use each type of Query 231
f. Optimize CRUD Operations 232
9. Resources 241
a. What are Resources 241
b. Adding and Using Resources 242
Unit IV
10. Forms 243
a. Basics of Forms 243
b. Existing Forms 244
c. Templates available in AX 2012 249
d. Creating New Forms 254
e. Using Data Sources 255
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 14
f. Using resources in a form 259
g. Joining Data Sources 267
h. Methods in Forms/Methods in Data Sources/Form Data Source Field Methods 274
i. Method Calling Sequence 280
j. Placement of code 281
k. Adding filters to forms 282
l. Bound and Un bound Controls/Methods on Controls 285
m. The power of Display and Edit Modifiers 296
n. Using ManagedHost, Splitters etc. 298
o. Calling a form from code 303
p. Identify existing forms and personalization 305
11. Reports 309
a. Basic Report Wizard [MorphX Reports] 312
b. Important Methods in Reports 317
c. The Power of Programmable Sections 318
d. Auto Design and Generated Design 319
e. SQL Server Reporting Services 321
i. Using Visual Studio to Develop SQL Server Reports 326
ii. Auto design and precision design 328
iii. Adding the report to AX 330
iv. Deploying the reports from AX 331
v. Opening and editing existing reports 333
12. Menus and Menu Items 349
a. Different Types of Menu Items 349
b. Menus 352
13. Files 359
a. Reading and Writing Text Files 359
b. Creating CSV Files 362
c. Reading and Writing XML Files 364
d. Exporting the Data to Excel Documents 367
14. Frameworks 369
a. Runbase Framework 369
b. SysOperationFramework 379
c. Number Sequence Framework 389
15. Miscellaneous
a. Using .NET Classes in AX 397
Appendixes
16. New improvements and CIL in AX 2012 405
17. Installation and configuration of AX 2012 407
18. Using Debugger in AX 409
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 15
a. Debug Standard AX X++ Code 409
b. Configuring Debugger in AX 410
c. Using configuration utility 411
19. Label Wizard and Label Editor 417
20. Basic Administration 427
21. Interesting tools in AX development environment 437
22. Best Practices and their Requirement 443
23. Tips while working in AX Environment 447
Links - 449
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 16
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 17
Introduction
Designed for midsize and larger companies, Microsoft Dynamics AX (formerly Microsoft
Axapta) is a multi-language, multi-currency enterprise resource planning (ERP) solution. Microsoft
Dynamics AX is fully customizable and extensible through its rich development platform and tools.
As said, Microsoft Dynamics AX supports support for n languages and n number of currencies
with localization settings for various countries. Microsoft Dynamics AX is the complete ERP solution for
enterprises that provides core ERP functionality for financial, human resources and operations
management.
Microsoft Dynamics AX delivers a rich industry platform on which partners can build their own
vertical applications for mid-sized organizations and divisions of large enterprises. The solution supports
the primary and secondary processes of organizations in multiple industries. As said, AX is a powerful
tool to manage the entire enterprise day to day operations with support to various industries included in
one application.
Its a global solution that is scalable and agile. The solution is made industry focused by
embedding core functionality for the industries includes manufacturing, distribution, public sector, retail
and service industries.
Reading this Book
Firstly, Id like to thank you for choosing the book as your choice of learning Microsoft Dynamics
AX. Im sure, you dont get bored of even a single line while you go through the topics. There is a vast
coverage of various topics in the book which can be used by a fresher through an experienced enterprise
developer of AX.Really, it was a great experience to write a book when I learned AX from its core and a
lot which I never did.
The book was written with a lot of research done to apply the thoughts which might be useful to
reader to make the subject easiest possible. As said, I dont make the readers bored, but request to read
the book from first to last line to get complete knowledge on the technology. The book has lot of
material and is covered in practical point of view with scenarios.
There is a lot of stuff inside in addition to the indexed content which is not given in index. I felt
that they are internal part of few topics and the lines are not included in index to avoid the size of the
index looking at which you may get bored. I suggest you to read each topic completely which will give
you some or the other point which may be useful to you.
This book focuses more on the examples and explanation in plain and simple English which can
be understood by a simple novice also.
Structure and Approach
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 18
The following section will introduce on how and the way the book is organized.
There are 3 units and an appendix which are organized to cover a major part of AX. All the topics
includes the basic requirement, technical requirement and samples to practice the topics. This book
covers Microsoft Dynamics AX 2012 and discusses some topics of 2009 also to compare various features
which can be helpful for developers as many Organizations are still using Dynamics AX 2009 and are
trying to upgrade themselves. Each of the unit is divided into topics and sub topics to give an extensive
coverage of the topics. The book was planned to cover basic and advanced topics of Microsoft Dynamics
AX but the topics were restricted due to a fact that reader get bored of very large books. The
subsequent topics will be covered in the subsequent books released from DAX Softronics (India) Pvt. Ltd.
You can find the updates from www.daxsoftronics.com or www.axaptaschool.com .
Unit I covers the architectural view of AX and the comparison of DAX with ERP and others. This
unit also covers the basic environment that is used to work at the client end in Microsoft Dynamics AX.
Unit II covers the basic technical stuff required to solve AX technical requirements. The topics in
this unit cover basic programming, tables to queries etc., which are used throughout the book. This unit
also covers complete Object Oriented Programming of X++ extensively from fresher perspective. In
addition, this will cover the types of classes available and some foundation classes. I request the readers
to read the topics until they feel that they have understood the concept up to the mark which is useful
to understand the continued units/topics in the book.
Unit III covers the advanced technical stuff that is required to extend/expand the AX
technical/functional requirements. These mainly include the UI components i.e. Forms, and Reports etc.
and the entry point of the application, Menus and Menu items.
A handful of appendixes are given to cover various topics and some key points which are
required while developing with AX. I felt that these will be useful throughout development process and
hence, added in this section.
The book is covered in practical point of view where developers can learn by doing which is the
best approach followed. I request you to send all the queries, feedback which will always contribute for
the betterment of the next version of the book and we always appreciate that. Readers who feel some
chapters bore, I suggest them to read the topic at least once even if bored and Im sure, youll find some
interesting points to note which may be useful further.
Last but not least, Practice makes man perfect, a fact which should be applied while learning
any technology. Please have a good machine installed and configured with Microsoft Dynamics AX to
have good practice on the technology from which you will always get great returns. Id really love to
hear your feed back at mailto:kumar@axaptaschool.com.
Thanks,
T A S K Kumar.
Axapta School.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 19
Unit I
1. Architectural Overview
2. ERP and DAX
3. MorphX Environment
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 20
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 21
Architectural Overview
3-Tier Architecture
Architecture is nothing but the flow of data from one part of the application to another part or
one application to another application to complete the operation and get expected result. Any
application/program has its own way of working and its own architecture. AX architecture is defined for
extracting the best performance out of the application and makes the user get best experience working
with AX.
In Microsoft Dynamics AX, there is a 3-tier infrastructure with a database server, an application
object server (AOS), and a client.
As from the above figure, Tier One is the client where users work. This holds the UI.SRC: MSDN
In the above figure, UI or client is called as MorphX. This is IDE which is used to communicate
with AX server. Custom AX development and modification is done with its own IDE, MorphX, which
resides in the same client application that a normal day-to-day user would access, thus allowing
development to take place on any instance of the client. Since the Dynamics AX 2012 version,
development can also be performed in Microsoft Visual Studio 2010 through a Visual Studio plug-in.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 22
MorphX is an integrated development environment in Microsoft Dynamics AX that allows
developers to graphically design data types, base enumerations, tables, queries, forms, menus and
reports. In addition to design of application objects, it also allows access to any application code by
launching the X++ code editor.
An Application Object Server (AOS) is a core component of the Microsoft Dynamics AX 2012
installation and is installed by using Setup. An AOS enforces security, manages connections between
clients and the database, and provides the foundation where Microsoft Dynamics AX business logic is
executed. An AOS is implemented as a Microsoft Windows Service and is listed in the services as
Microsoft Dynamics AX Object Server 6.0$InstanceName. When a client requests some operation like
posting Sales Order, server handles the request and sends the response to the client.
Finally, backend tier is used to store the data. AOS will not store any data except executing
business logic and communicate with backend to get and store data. Usually, Microsoft SQL Server 2005
is used for AX 2009 and Microsoft SQL Server 2008 is used for AX 2012 for storing data.
Layered Model
In Microsoft Dynamics AX, a layer system is used to manage elements. The USR layer is the top
layer and the SYS layer is the bottom layer, and each layer has a corresponding patch layer above it.
These layers are used to organize the objects of AX standard package and the customizations done by
various users or developers at various levels.
The following table describes the application object layers in Microsoft Dynamics AX:
Layer Description
USR The user layer is for user modifications, such as reports.
CUS The customer layer is for modifications that are specific to a company.
VAR Value Added Resellers (VAR) can make modifications or new developments to the VAR layer as
specified by the customers or as a strategy of creating an industry specific solution.
ISV When an Independent Software Vendor (ISV) creates their own solution, their modifications are
saved in the ISV layer.
SLN The solution layer is used by distributors to implement vertical partner solutions.
FPK The FPK layer is an application object patch layer reserved by Microsoft for future patching or
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 23
other updates.
GLS When the application is modified to match country or region specific legal demands, these
modifications are saved in the GLS layer.
SYS The standard application is implemented at the lowest level, the SYS layer. The application
objects in the standard application can never be deleted.
Each layer has a corresponding patch layer that can be used to incorporate updates to your
application or to store conflicts when you import models into a layer. The following table shows the
layers along with the corresponding patch layers:
Layer Patch Layer
USR USP
CUS CUP
VAR VAP
ISV ISP
SLN SLP
FPK FPP
GLS GLP
SYS SYP
The patch layers are designed to make it easy to incorporate updates in your application. The
basic idea is that when a minor update or correction is made, it is distributed in a patch file. Modified
objects in the patch file are automatically used because they take precedence over the regular
application objects.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 24
Advantages of Layer Files:
The fact that each layer is saved in a dedicated file means that it is easy to locate the file to
backup. It also means that you can easily remove undesired modifications by deleting the layer file.
The layers ensure that
Any users of the Microsoft Dynamics AX application, whether a distributor, a business
partner or an end user, can customize the Microsoft Dynamics AX application to suit
their needs.
The standard application is never overwritten.
When you delete an object, you delete it in the current layer only.
Model Store:
Models were introduced in Microsoft Dynamics AX 2012 to help partners and customers more
easily install and maintain multiple solutions side by side in the same layer. This topic introduces the
concept of models, and describes how models relate to layers and label files. This topic also describes
the model store, which is a database in which all application elements for Microsoft Dynamics AX are
stored.
A model is a set of elements in a given layer. Each layer consists of one or more models. Each
layer contains one system-generated model that is specific to that layer. Every element in a layer
belongs to only one model. In other words, no element can belong to two models in the same layer, and
every element must belong to a model.
Models are stored in the model store. The model store is a database in which all application
elements for Microsoft Dynamics AX are stored. Customizations are also stored in the model store. The
model store replaces the Application Object Data (AOD) files that were used in earlier versions of
Microsoft Dynamics AX. Models that have been installed in the model store are used at run time.
In Microsoft Dynamics AX 2012 R2, the model store was moved into a database that is separate
from the business database.
Models can be exported to files that have the .axmodel extension. These files are called model
files. Model files are deployment artifacts. Model files can be signed with strong name signing and
Microsoft Authenticode signing.
The following is the way how models can be used when doing side-by-side customizations:
In earlier versions of Microsoft Dynamics AX, multiple partner solutions could not exist side by
side in the same layer. However, models now enable side-by-side customizations. Additionally, the
following improvements help you work with side-by-side customizations:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 25
The development environment for Microsoft Dynamics AX lets you create a project
for each model that is installed. Therefore, you can quickly see all the installed
customizations in a layer for a given model.
When you import a model, elements in the model that you are importing may
conflict with another model in the same layer. You can now create a conflict model
in the patch layer that is associated with the layer that you are working in. You can
then resolve the conflicts in the conflict model. In earlier versions, no warnings
about conflicts were displayed. Instead, elements were just replaced.
You can now leave the rest of the layer intact when you uninstall a model. In earlier
versions, if you wanted to uninstall customizations, you had to either remove the
customizations manually from the AOT or remove the layer.
By default, each layer contains a model that corresponds to the name of the layer. You can add
additional models to layers that you have access to, depending on your license configuration. You can
have different versions of the same element in models that are in different layers, but each element
within any one layer must be unique.
If the same element exists in two models, and you try to import both models into the same layer,
the element that exists in both models will cause a conflict. To mitigate the conflict, you can choose one
of three options:
Abort the operation and leave the model store unchanged.
Overwrite existing definitions with the definitions in the new model.
Create a new model in the patch layer that contains the conflicts.
If you choose the third option, a conflict model is created in the patch layer that corresponds to the
layer you are importing the model into. The conflicting elements are moved to this model. You can then
decide how to resolve the conflicts.
Application Development and Run Time Environments:
The Dynamics AX 2012 development and run-time environment supports the following three
ERP applications:
Rich Client Applications: These are developed using MorphX development environment and is
run by Dynamics AX runtime. It is better to write code server-centric so that duplication of code will be
avoided and the communication is done using ports [i.e. Microsoft RPC communication technology] is a
key asset in Dynamics AX. This makes customers, vendors, partners and employees to access the
information which is accessible to them directly through web portals, which can be personalized and
role based.
Web Client Application: We develop these applications using MorphX development
environment and Windows SharePoint Services. From AX 2009, we are using Visual Studio for
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 26
developing web parts for enterprise portals. Business Connectors which will be discussed later in this
book are used.
As the web is playing an important role and has its own importance being most used platform
for electronic work, portals are used extensively with Enterprise Portal and its advancements
incorporated into Microsoft Dynamics AX with SharePoint integration.
Client Applications used for Integration: We develop these applications using MorphX and/or
Visual Studio environments. We use business connectors for developing these kinds of applications.
Most of the cases, we do XML Document integration while developing these applications.
Application Frameworks
The Dynamics AX application framework is set of API classes and elements that are used for
development of most of the features in ERP to get the best user experience. The book covers most of
the elements and the new features that are released in different versions of Microsoft Dynamics AX.
Some of the important APIs covered are as follows:
Runbase Framework
SysOperationFramework
Number Sequence Framework
Application Integration Framework
We will discuss the above frameworks in subsequent topics throughout the book where ever
applicable.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 27
ERP and AX
What is ERP
Enterprise Resource Planning (also known as ERP) is an effective approach that most businesses
implement to enhance their productivity and performance. Known as a systematic approach that most
industries use to organize resources as well as improve efficiency and performance, ERP is usually
implemented by corporations to centralize the databases and functions of every department in a single
system. The system features various components including software modules, which integrate and
manage all the business and private records of firms.
Enterprise resource planning (ERP) systems integrate internal and external management
information across an entire organization, embracing finance/accounting, manufacturing, sales and
service, customer relationship management, etc. ERP systems automate this activity with an integrated
software application. The purpose of ERP is to facilitate the flow of information between all business
functions inside the boundaries of the organization and manage the connections to outside
stakeholders.
The following are common functional areas covered in an ERP System. In many ERP Systems
these are called and grouped together as ERP Modules:
Financial Accounting
General Ledger, Fixed Asset, Payables, Receivables, Cash Management, Financial Consolidation
Management Accounting
Budgeting, Costing, Cost Management, Activity Based Costing
Human Resources
Recruiting, Training, Payroll, Benefits, Retirement, Separation
Manufacturing
Engineering, Bill of Materials, Work Orders, Scheduling, Capacity, Workflow Management,
Quality Control, Manufacturing Process, Manufacturing Projects, Manufacturing Flow, Product
Life Cycle Management
Supply Chain Management
Supply Chain Planning, Supplier Scheduling, Order to Cash, Purchasing, Inventory, Product
Configuration, Claim Processing
Project Management
Project Planning, Resource Planning, Project Costing, Work Break Down Structure, Billing, Time
and Expense, Performance Units, Activity Management
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 28
Data Services
Various "selfservice" interfaces for customers, suppliers and/or employees
Access Control
Management of user privileges for various processes
Advantages
The fundamental advantage of ERP is that integrating the processes by which businesses operate
saves time and expense. Decisions can be made more quickly and with fewer errors. Data becomes
visible across the organization. Tasks that benefit from this integration include:
Sales forecasting.
History of every transaction through relevant data compilation in every area of
operation.
Order tracking, from acceptance through fulfillment
Revenue tracking, from invoice through cash receipt
ERP systems centralize business data, bringing the following benefits:
They eliminate the need to synchronize changes between multiple systems
consolidation of finance, marketing and sales, human resource, and manufacturing
applications
They enable standard product naming/coding.
They provide a comprehensive enterprise view. They make realtime information
available to management anywhere, any time to make decisions.
They protect sensitive data by consolidating multiple security systems into a single
structure.
Benefits
ERP can greatly improve the quality and efficiency of a business.
ERP provides support to upper level management to provide them with critical decision
making information.
ERP also creates a more agile company that can better adapt to situations and changes.
Disadvantages
Customization is problematic.
ERP can cost more than less integrated and/or less comprehensive solutions.
High switching costs associated with ERP can increase the ERP vendor's negotiating
power which can result in higher support, maintenance, and upgrade expenses.
Integration of truly independent businesses can create unnecessary dependencies.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 29
Training requirements take resources from daily operations.
Requires a time, planning and money.
The limitations of ERP have been recognized sparking new trends in ERP application
development, the four significant developments being made in ERP are, creating a more flexible ERP,
Web-Enable ERP, Inter-enterprise ERP and e-Business Suites, each of which will potentially address the
failings of the current ERP.
Depending on the organization size, capacity and needs, ERP is divided into 3 markets as follows:
Large Enterprise ERP (ERP Tier I)
The ERP market for large enterprises is dominated by three companies: SAP, Oracle and
Microsoft.
Midmarket ERP (ERP Tier II)
For the midmarket vendors include Infor, Lawson, Epicor and IFS etc.
Small Business ERP (ERP Tier III)
Exact Globe, Syspro, NetSuite, CDC Software, Activant Solutions etc. round out the ERP vendors
for small businesses.
AX and ERP
Microsoft Dynamics AX is the complete ERP solution for enterprises that provides a purpose-
built foundation across five industries, along with comprehensive, core ERP functionality for financial,
human resources and operations management. It empowers your people to anticipate and embrace
change so your business can thrive. All of this is packaged in a single global solution giving you rapid time
to value. Microsoft Dynamics AX supports multiple companies, multiple currencies, and multiple
languages.
Microsoft Dynamics AX can provide your organization with business value in a single ERP solution
that extends into every area of your operations to help you:
Improve productivity
Manage change and growth
Compete globally
Simplify compliance
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 30
Solutions for the industries:
Microsoft Dynamics AX delivers a rich industry foundation on which partners build packaged
applications for niche verticals, such as high-tech manufacturing, architecture and engineering, and
specialty retail. These industry capabilities can help you improve your ability to cope with individual
market dynamics by delivering breakthrough innovation in a single ERP solution for key industries
including:
Manufacturing:
o This covers the requirements of Lean manufacturing, process manufacturing
and discrete manufacturing which is a built solution in AX and reduces the
complexity in implementing the solution for manufacturing industry.
Distribution:
o This covers wholesale, warehouse management and distribution of the
materials which is built in and reduces the implementation cost and risk for the
industry.
Retail:
o Solution provides implementation for merchandizing, point of sales and store
management
Services:
o Service industries support is provided with this including project and resources
operations; talent and skills management
Public Sector:
o Support for public sector industries which includes grants management,
commitment and fund accounting etc.
Going back to history, development of Axapta began in 1983 at Danish company Damgaard Data
A/S. The software was mainly targeted at the European market, though the North American market
grew rapidly following the release of Axapta 2.1 in 2000. Following the merger of the two Danish
companies Navision and Damgaard, Axapta was to be known as Navision Damgaard Axapta for versions
2.5 and 3.0 (up until 3.0 SP5). Microsoft acquired Navision Damgaard during the summer of 2002.
Navision Damgaard Axapta was first renamed to Microsoft Business Solutions Axapta, then to Microsoft
Dynamics AX for versions 3.0 SP6, 4.0, 2009 and now, 2012.
Above table is taken from different sources which explain the core capabilities of Microsoft
Dynamics AX very clearly. You can check the references section of this book for the sources of the
information gathered.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 31
Modules [core ERP capabilities]: SRC: MSDN
FINANCE MANAGEMENT
General ledger
Accounts receivable and
payable
Bank management
Budgetary control
Share service support
Compliance management
HUMAN CAPITAL MANAGEMENT
Organizational and workforce
management
Recruitment and selection
Development, training, and
performance management
Employee self-service portal
Expense management
BUSINESS INTELLIGENCE AND
REPORTING
Standard, ad hoc, and
analytical reports with
Microsoft SQL Server
Reporting Services
Role Tailored, predefined,
multidimensional data cubes
Dashboard views of key
performance indicators
Expense management
PRODUCTION
Material and capacity planning
Resource management
Job scheduling and sequencing
Product configuration
Shop floor management
SUPPLY CHAIN MANAGEMENT
Inventory management
Multisite warehouse
management
Trade agreements
Order promising
Distribution planning
Quality management
PROCUREMENT AND
SOURCING
Direct and indirect
procurement
Purchase requisitions
Supplier relationship
management
Vendor self-service portal
PROJECT MANAGEMENT AND
ACCOUNTING
Project accounting and
invoicing
Project cost control
Work breakdown structures
Integration with Microsoft
Project
SALES AND MARKETING
Sales force and marketing
automation
Lead and opportunity
management
Sales management
Microsoft Dynamics CRM
connector
SERVICE MANAGEMENT
Service orders and contracts
Service calls and dispatching
Repair management
Service subscription
We can also combine/integrate Microsoft Dynamics AX with other Microsoft Products and
Technologies. Some of them are as follows:
Developer Tools:
o Microsoft Visual Studio and Microsoft .NET
o Windows Communication Foundation and Windows Workflow Foundation
Business Productivity Solutions:
o Microsoft Outlook, Excel, Word
o Microsoft Lync and SharePoint
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 32
Application Platform:
o Microsoft SQL Server
o Microsoft BizTalk
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 33
MorphX Environment
The MorphX Development Suite is the integrated development environment (IDE) in Microsoft
Dynamics AX 2009 used to develop and customize both the Windows interface and the Web interface.
An IDE integrates development functions such as designing, editing, compiling, and debugging within a
common environment. With MorphX, common operations such as building or modifying forms, menus,
and reports are done using drag-and-drop techniques with little or no coding.
Development environment features in Microsoft Dynamics AX:
Microsoft Dynamics AX MorphX is an integrated development environment (IDE) for developing
in Microsoft Dynamics AX.
Visual Studio is an alternative development environment for web, report and managed code
development.
The Application Object Tree (AOT) provides a uniform and compact viewing repository.
Drag-and-drop functionality is supported for many programming tasks.
Projects help organize and track customized applications in Microsoft Dynamics AX.
Microsoft Dynamics AX Rich Client is the primary client to access Microsoft Dynamics AX
functionality. Most forms displayed in the rich client are designed by using the MorphX development
environment. Developers can access the developer tools through the MorphX IDE in the Microsoft
Dynamics AX client or through Visual Studio Tools in Visual Studio.
The Microsoft Dynamics AX application is built of elements that are stored in the model store in the
SQL Service database. For example, the following element types make up part of the application:
Fields and Tables define data structure.
Forms and Menus define how a user interacts with the application.
Classes and Methods are code objects that define business logic.
The Application Object Tree (AOT) provides a visual representation of the elements that comprise
the application.
Object-oriented design and Inheritance are key concepts that form the basis of the application.
Application Object Tree
The Application Object Tree (AOT) provides a visual representation of the elements that comprise
the application.
A full definition of all the element types in Application Object Tree (AOT) can be found in the
development training material and developer Help files. Some of the root element types include:
Data Dictionary contains objects that define basic data structure.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 34
Tables contain a group of associated fields. For example the VendTable contains fields
relevant to Vendors. Fields on a table contain individual parts of data. An example is,
AccountNum, one of the fields of VendTable contains the vendor account number. Fields on
a table inherit properties from a base data type or an extended data types.
Extended data types define a data type and extended properties of that base or another
extended data type. There are various basic data types such as a string, integer, date, time
and enum. For example, AccountNum is a string extended with properties including a length
of 20 characters and has few other properties set.
Base Enums are enumerated text data types. These are predefined text values that are
referenced by an integer value in the database. For example, Gender is an enum that gives
the user only two options (Male or Female) as to know gender of an employee.
Classes contain code that is used in the application.
Forms have the layout of all of the forms used throughout application. Forms are used to view
data in the Microsoft Dynamics AX client.
Visual Studio Projects display any development projects created in Visual Studio. Once we create
a project in Visual Studio, it should be added to Application Object Tree to get into the AOT, or
we dont find that project in AOT.
Menus define forms, reports and executable units of code that appear in the menu in the
Microsoft Dynamics AX rich client.
Application Object Tree will be covered extensively in subsequent chapters in following text.
The following image shows how Microsoft Dynamics AX rich client and MorphX looks: [Please note
that the image shown here is of Microsoft AX 2012]
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 35
In the above image you see, following are the component that can be discussed about:
Breadcrumb Bar: The bar in which you are able to see the company selected [DAT], the
module selected [Accounts payable]. This is used to navigate through the companies
configured, modules [also called as verticals] available. This can also be called as
Address bar.
On the left side, we are able to see a pane, called as Navigation pane through which we
can open the elements like forms/reports etc. which can be used by end users. This is
used for navigation between different forms and reports and other elements in a
module and you can also navigate between modules.
You can also see File menu and few shortcuts which can be used to find help, about
Microsoft Dynamics AX version and models information, to switch between
Development and User workspace etc.
The central part, which occupied most of the space, is called as content pane in which
you will find the menu items which are used by end user to open the forms/reports. If
you click on a particular menu item, you will be able to see that element on your screen.
Finally, there is a status bar which will give you information about unread notifications,
the company, currency information etc. You can also configure/customize this to display
the items you need and hide items you dont need.
The above image is only user workspace and developer workspace looks as shown in coming image:
Status Bar
Content Pane Bread Crumb Bar
File Menu and
Shortcuts on
right
Navigation Pane
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 36
The main difference between AX 2012 and AX 2009 regarding to the environments is, in AX 2009,
there is only one workspace which is used by both developers and users. Whereas in AX 2012, there are
2 workspaces namely, User Workspace and Developer Workspace.
The following are the components we identify in the above figure:
A menu bar and shortcut bar which are used to do the standard operations, in which we will
discuss some of them in the following sections.
The Application Object Tree (shortly AOT), in which you are able to see a tree structured
nodes which are used for development of various components used throughout ERP.
A properties window which displays the properties of the objects in AOT.
Code editor, in which developers can write, edit, compile and execute the code. Code is
written in an object oriented programming language called X++, which will be covered in
coming sections.
A compiler output window in which you can find errors/warnings etc. once you compile the
code. The usage of these windows is covered in debugging section of this text.
Finally, status bar will give you information about the environment like, the model, layer etc.
you are working on. You can customize these to hide or make visible as per your
requirement always.
As we covered enough introduction stuff, its time to get into the basic development stuff. In
this text, Im starting from X++ programming rather than the regular approach to make developers feel
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 37
the environment and get hands on experience and make familiar to the environment which will help at
later stage while developing applications. Those who like to see programming later can follow the AOT
and get into the programming at later stage when they feel that they are familiar. Id like to receive the
feedback on the approach for better organizing the structure of the book in further editions.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 38
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 39
Unit II
1. X++ Introduction
2. Object Oriented Programming in X++
3. Data Dictionary
4. Macros
5. Queries
6. Resources
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 40
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 41
X++ Introduction
X++ is the primary programming language used in the MorphX Development environment. The
following are important features of X++:
X++ is an Object Oriented Programming Language very similar to Java and C#. Due to
this, programmers feel easiness and very convenient working with the language.
Complex business logic for accounting and business management systems can be built
very easily with the help of many integrated SQL commands.
Programmers can access the existing system classes that can be used to extend the
functionality of the application.
Characteristics of X++:
Reliable: X++ provides extensive compile-time checking, followed by a second level of
run-time checking. Language features guide programmers toward reliable programming
habits. The memory management model is simple; objects are created by using a "new"
operator and there is automatic garbage collection.
Interpreted and Dynamic: Benefit from faster development cycles - prototyping,
experimentation, and rapid development, versus the traditional compile, link, and test
cycles.
Interoperable: Components in Microsoft Dynamics AX are seamlessly available to any
application supporting .NET, and conversely X++ is able to consume external managed
code and COM objects.
Case Insensitive: X++ is case insensitive, but, some naming conventions are followed for
better understanding of code.
No pointer arithmetic: X++ doesnt support pointers, which eliminates complexity in the
language.
Development Tools:
All elements that comprise the Microsoft Dynamics AX application (classes, forms, tables, and
more) are organized in the Application Object Tree (AOT). This is a central, organized index to all
application elements, displayed in a graphical tree view.
Microsoft Dynamics AX customizations are developed by modifying one or more elements from
the AOT. These elements are usually assembled in a project, or, in other words, a container for elements
that implement an application in Microsoft Dynamics AX. Projects help manage development efforts by
organizing units of functionality.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 42
As developers create new projects or modify existing projects, they use a series of development
tools. These tools include the following:
X++ Editor
X++ Compiler
X++ Debugger
Visual Studio
Visual Studio Debugger
All the above tools except Visual Studio are accessible from Microsoft Dynamics AX
Development Workspace. To open the development workspace, press Ctrl + Shift + W from regular user
workspace or simply add development to the command line parameters to start development
environment using AX client shortcut.
The above described tools are discussed as follows.
X++ Editor
This is the place where code is written, compiled and executed. Most AOT nodes [methods]
which have code can be opened with a double click which will open the below editor and show the lines
of code. This editor can also be started by selecting View Code in the right click context menu. The
following image shows the look and feel of X++ code editor:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 43
The above window (image) consists of two panes:
One for displaying the current methods or jobs.
The right pane displays the X++ code.
There are many toolbars buttons available in heading of X++ editor window. These are used for
regular operations like New, Save, Compile, Run etc. You can find the shortcuts for these in Shortcuts
section of appendixes. To find the use of specific button, point at the button using your mouse pointer,
you will find the use of the button.
X++ code editor uses following color codes for better understandability of developer:
Color Code type
Blue Reserved words
Green Comments
Dark Red Strings
Bright Red Numbers
Purple Labels
Black Everything else
As we know the basics of X++ code editor, it is time to start the programming in X++.
My First X++ Program
static void job1 (Args _args)
{
info(Hello World!);
}
The above program displays Hello World! in a window similar to a popup. This window is called
as infolog and is used most frequently to display the output on the screen.
The following points can be noted from the above program:
Every block of program should be enclosed in { and }.
Every statement in X++ program should end with ;
Now, lets try to understand where to write the above program and how to execute that, which
covers what is job, which is used in the above program.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 44
Jobs
A job is a stand-alone block of code in Microsoft Dynamics AX that can be run from the X++
editor. Jobs are used basically for testing code during development and running batch processes within
Microsoft Dynamics AX that affect large amounts of data.
This section will explain you about using the Jobs for writing code snippets, executing them
directly from X++ editor about compiler and basics of X++ compiler. Most of the code for any
requirement is written in classes, methods, tables and/or forms. As said above, jobs are useful for
testing code before using them directly in methods or classes. Following is the code when you create a
Job:
static void Job1(Args _args)
{
}
Note: You can always change the name of Job, which is always advisable to give a better name
as per naming conventions and to remember the purpose of the Job.
To create a new Job, right click on Jobs node in AOT, select New Job, You will see the X++ editor
opening with the newly created job. Rename this job to MyFirstJob. We will write info to display some
output:
static void MyFirstJob(Args _args)
{
info(Hello World!);
}
The above program display a window called as infolog with a line Hello World!. To execute the
above code snippet, first compile the Job, later execute the job. You can find detailed step by step
approach for compilation, debugging and executing in Appendixes. Simply, F7 is used to compile and F5
is used to execute the job. If you get any errors, please check the following frequent errors which an
inexperienced developer may encounter:
Missed semicolon (;) after end of statement info().
If you are working on AX 2009, you have to give a semicolon (;) before info() as the
declaration section should be terminated by a semicolon (;) in AX 2009, which is not
required in AX 2012.
Missed ( or ) or { or } or or misspelled keywords.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 45
Please check for the above errors, resolve them, re compile and execute. You should see the
output as expected with an infolog.
Finally, Args is used to pass arguments from caller. We will discuss about Args more clearly with
samples in further sections of this book.
Comments:
Comments are used to describe about a line of code or the program. There are multiple types of
commenting in X++, used for different purposes. Though it is not mandatory, it is advisable to comment
consistently where ever applicable for the following:
What does the code does and how the parameters are used.
Who made the change and why the change was made for future use.
Note the following points before comments are used:
Comments can be directly inserted into the lines of code at any place and any
point of time.
Comments are ignored by the compiler i.e. comments are not compiled either
they have executable statements or plain English statement. So, if a code is not
required (while testing or for time being), you can comment those lines of code.
When you comment, comments will turn the code into green in the editor. This
makes you identify the commented lines differently.
Some commenting styles are as follows:
Single line commenting, done using "//". This will comment only one line at time.
// This is a single line comment.
To comment multiple lines, we use block comments "/* */". /* will start the comment
and will comment line(s) until you end with */. Please note, if you forget the end, you
may encounter an error.
/*This is a block comment.
This can have more than one line. */
To do comments "TODO." To do comments appear in the compiler's
Tasks tab page. These are used to indicate that you have to do some task or add some
lines of code.
XML documentation comments. These are used to write notes about a method, the
parameters used etc., precisely the purpose of the element. An example is as follows:
///<summary>
///Comment using XML tags to distinguish sections.
///</summary>
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 46
These XML documentation comments are used to build XML documents that will have the
developer documentation, which can be used to display help in X++ editor further.
As we became familiar using the jobs, how to write a job, compile that, execute that, its time to
get into further step, declaration of variables, following with advanced stuff as follows.
Variable, a memory block used to store the data which is used in program to get the expected
result. Variable holds the data when the program executes. To declare a variable, we need to identify
the type of data that is stored in the variable. For example, age of a person is an integer value whereas,
the amount that should be collected from customer or to be paid to vendor is a real value. Once the
type is identified, declare a variable following the naming conventions. We will see how to declare, use
the variables in this section. Following is a sample program which demonstrates how to declare
variables:
static void Declaring Variables(Args _args)
{
int age; //This declare a variable named age, which can be used to store integer value.
}
In the above program, we have declared a variable named age. This variable can store numeric
digits, positive and negative but, will not accept characters. Now, lets assign value to this variable, an =
is used to do that as follows:
age = 28;
The above statement will assign the value 28 to age, when you try to retrieve value of age, 28
will be returned. The following chart describes about the available data types in X++ and the values that
can be stored.
Data Type Description Keyword Example
Declaration
String A string is a set of characters. X++
supports following types of strings:
aligned [left or right], fixed length or
variable length. The length of a string
can be maximum of 999 characters.
Null value is represented by empty
string.
Str CustomerName
Integer An integer, also named a natural
digit, is a number without decimal
point. Null value is represented by 0.
Int 1090
Real Real values, also called decimals, are
digits with a decimal point. Null value
Real 3.14
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 47
is represented by 0.0.
Date The date type contains day, month,
and year.
Date 12/22/2012
UTCDateTime This type contains year, month, day,
hour, minute and second. Null value
is represented by 1900-01-01.
utcDateTime 12/22/2012
04:00:59 am
Enum Enum values are represented by
integers internally in database
though we are able to see as strings
in frontend. The first literal has the
number 0, the next number 1 and so
on. You can use enums as integers in
expressions. Element value with 0 is
assumed as null value.
Must be declared
as a BaseEnum
first
Gender
Boolean Booleans can only contain the values
false and true. The values false and
true are predefined in X++ and
recognized by the compiler.
Boolean TRUE
Time This type contains hours, minutes,
and seconds. To declare a time, use
the system type timeOfDay. Null
value is represented by 00:00:00.
timeOfDay 04:00:59
GUID Global Unique Identifier (GUID) is a
reference number which is unique in
any context.
Guid {5D2503A0-4S80-
55D3-0P9C-
2908H82C3804}
Int64 A large integer, represented by 64
bits.
int64 11234567890
The chart above shows the types, the values that can be stored by variables of that type and a
sample declaration/value that is stored when we use the type. The above types are called as primitive
types or basic types which are used to store basic values. Note that, each variable can store only one
value at a time. If you assign one more value, previous value will be replaced with newly assigned value
and if you try to store multiple values, you will get error in return.
Syntax for declaring a variable is as follows:
<dataType><variableIdentifierName>;
Following are few examples for the above shown types:
int integerVariable;//This declares a variable of type integer
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 48
real realVariable; //This declares a variable which can store real values
str unboundStringVariable;//This variable stores string with maximum length
str 30 boundStringVariable; //This variable stores string with maximum of 30 characters
date dateVariable; //This variable stores date
boolean booleanVariable; //This variable can store a Boolean value i.e. True or False.
Now, lets understand assigning values and understand some basic rules for assignments:
int age = 28; //This is direct assignment while declaration
int age; //Simple declaration.
age = 28; //This is assigning values to variable after declaration
age = 27; //This will overwrite the value 28 with 27. Here onwards, age will have 27.
You can also declare several variables of same type in one statement as follows:
int age, amount, distance;
Now, lets assume a scenario where we need to store multiple values in a single variable, for e.g.
we need to store prices of an item in a single variable. We cannot use the above declaration for this.
Instead of the above declaration, we use composite data types for this scenario. Composite types are
the data types which can be used to store multiple values of a single type or different types. Following
are some of the composite data types available in X++:
Data type Description
Array An array is a list of items with the same data type and the same name; only
the index differs.
Container A container is a dynamic list of items that can contain primitive data types
and some composite data types.
An example of declaring an array is as follows:
int unlimtedArray[]; // Unlimited index values
real limitedArray[20]; // maximum of 20 values
limitedArray[2] = 9.8;
We use [] for declaring an array which can be used to store multiple values in a single variable as
shown above. In the above example, unlimitedArray is declared without anything in [], which can be
stored multiple numbers compared to second one, limitedArray, which can be used to store maximum
of 20 values. Finally, assigning values is not like an ordinary variables, instead, an index is used which
says the variable part to use for storing the value. In the above example, limitedArray[2] indicates that,
the second slot is used to store the value.
Note: Index in X++ starts with 1.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 49
As in the above example, an array variable can store multiple values of single type. Unlike
arrays, we can store multiple values of different types using containers. Operations on containers are
done using inbuilt functions. The following is an example for using a container:
container containerVariable; // the container is declared
int var1, var2;
str var3;
real var4;
containerVariable = [28, 27, "Sample string", 9.8]; // the container has 4 values set
info(conPeek(containerVariable, 3)); // the third element is printed
[var1, var2, var3, var4] = containerVariable; // other variables are set from the container
In addition to the above functions, we have few more which can be used to access containers as
follows:
Function Description
conPeek Returns the value being held in a specific position in the container.
conDel Removes a value from a specific position in the container.
conNull Returns an empty container.
conFind Finds the position in the container that a certain value is being held (if found).
conIns Inserts a value into a specific position in the container.
conPoke Replaces the value being held in a specific position in the container, with a new value.
conLen Returns the number of elements in the container.
Note: We will see few samples of container in coming chapters. We will discuss and use all the available
functions there.
Now, we are able to write a program, compile and execute, declare variables, use the variables,
and work with multi-valued variables (i.e. Arrays and Containers), its time to work with some complex
logic, like let us consider a scenario where if customer purchase amount is greater than 5000, a discount
of 2% will be given otherwise, no discounts. To solve similar problems, we rely on conditional
statements.
Conditional statements in programming define conditions under which certain operations are
performed. Conditional statements use logical expressions that are evaluated to either true or false. If
the evaluated expression is true, statements in condition will execute or, they will execute the default
operations. Conditional statements are basically 3 in number as follows:
If statement
Switch statement
Ternary operators
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 50
All these statements are evaluated using operators. An operator is a program element that is
applied to one or more operands in an expression or statement. For e.g. when you give a statement like
c = a + b, + is called an operator that operates between a and b and = is an operator that will assign the
output of a and b. + is called as arithmetic operator and = is called as assignment operator. Now, its
time to try conditional statements with few examples. Operators are used to evaluate an expression and
return the result to. Operators take one or two operands and assign to some variable. There are various
types of operators available like, Arithmetic operators, which will do arithmetic operations, logical
operators and assignment operators which does assignment etc.
The If statement:
The If statement is used to execute a block of statements based on the validity of condition
given in it. Please note that, block refers to the statements in between { and }. The condition is given as
expression with a single operand or multiple operands with operators may be used. This expression if it
is evaluated to true, block will be executed and appropriate action is taken otherwise. The following is
the syntax of if statement:
if(condition)
{
//Statement1;
//Statement2;
}
We will see a small example, where we will check for the bigger among 2 digits:
int a=3, b=4;
if(a > b)
{
info(a is bigger than b);
}
In the above case, a has value 3 and b has value 4. The condition a > b is evaluated to 3 > 4,
which will result into false, which will not execute the block and skip to the next statement(s) if
available, which are not there in our case. In the above case, if we replace the values of a with 4 and b
with 3, as the condition will get satisfied, you will get the result as a is bigger than b. If you observe the
above, we used 2 operands with an operator >. Sometimes, we may pass a single value without any
operators also, like a Boolean value, which can be used to execute the block if true. Lets check one
more program as follows:
boolean boolValue = false;
if(boolValue)
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 51
info(Should not enter this block.);
}
if(boolValue == false)
{
info(Should enter this block.);
}
if(!boolValue)
{
info(Should enter this block.);
}
Note that, the above program is a different when compared to the first as, we are using only
one variable in the condition, a Boolean. Output of the above program is as follows:
Should enter this block.
Should enter this block.
Lets try to understand why we got this output:
boolean boolValue = false;
if(boolValue)
{
//Here, boolValue is false and if will fail and will not allow to execute the block of
//statements if the condition evaluates to false. So we didnt see the statement of this
//block in output.
}
if(boolValue == false)
{
//In this case, condition is an expression, boolValue == false. == is an operator that will
//compare both the values and if they are equal, return true otherwise, false. Here,
//boolValue is false and the comparison will result true. So, this statement is executed.
}
if(!boolValue)
{
//Finally, !boolValue is, !(false), which will evaluate to true, note that ! is a logical NOT.
//So, the block gets executed successfully as the condition finally evaluates to true.
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 52
Now, its time to understand how to use multiple expressions to evaluate the condition. Let us
consider a scenario where if the purchase amount of customer is greater than 5000 and less than 10000,
a discount of 2% is given, otherwise, no discount as per business policy. Lets write a program to get the
condition work:
real purchaseAmount, discount;
purchaseAmount = 7200.25;
if((purchaseAmount > 5000) && (purchaseAmount < 10000))
{
discount = 2;
}
else
{
discount = 0;
}
In the above example, there are two expressions, purchaseAmount > 5000 and
purchaseAmount < 10000. In addition to these, we used &&, which is called as logical AND. This
means that, if both expressions are evaluated to true, then, the condition will evaluate to true,
otherwise false. The logical AND will check for the said condition i.e. true and true for both the
expressions. In the same way, you can add any number of expressions as per the requirement. You can
also use ||, which stands for logical OR, which means successful evaluation of either of the expression
is sufficient to execute the block by evaluating the condition. The second point to note is, if the
condition fails, the else part gets executed. Else is used when the if fails to provide an alternative. The
following example uses an if and else statements where, we are trying to evaluate few discount
percentages based on requirement.
real purchaseAmount, discount;
purchaseAmount = 12000;
if(purchaseAmount > 5000 && purchaseAmount < 10000)
{
discount = 2;
}
else
{
if(purchaseAmount< 20000)
{
discount = 3.5;
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 53
else
{
discount = 5;
}
}
The only thing Id like to say is, if you have multiple conditions to check, go for if - else if else
[This is not read as else and if else but if, else if, else ladder]. This is a multilevel check done if you have a
condition in a condition. You can always use multiple expressions in conditions. I leave this program to
you. Please try this program and understand the output for various values of purchaseAmount.
Note: If we pass a non-boolean value like integer, a non-zero will evaluate to true and zero will
be false.
Now, lets try a scenario, where the company likes to give some discount and special offer for
customers based on the total amount of stock they purchased. Following criteria defies this:
If the purchase amount is 20000, a discount of 10% and 1+1 offer is given.
If the purchase amount is 15000, a discount of 6% and 1+1 offer is given.
If the purchase amount is 10000, a discount of 5% and offer is not given.
If the purchase amount is 5000, a flat discount of 3% is given without any gift.
If the above conditions are not met, neither discount nor offer will be given.
int purchaseAmount = 15000; //Taking int as we are discussing about rounded digits
int discount;
str offer;
if (score == 20000)
{
discount = 10;
offer = 1+1;
}
else
{
if (score == 15000)
{
discount = 6;
offer = 1+1;
}
else
{
if (score == 10000)
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 54
discount = 5;
offer = ;
}
else
{
if (score == 5000)
{
discount = 3;
offer = ;
}
else
{
discount = 0;
offer = ;
}
}
}
}
If we observe the above program, choice is given to user, where he selects one of the available
choices, and the block is executed based on the selection of user. To make this kind of decision, we have
one more statement, Switch. Switch statement is a multi-branch control statement that will have an
expression whose result leads to a particular logic/block execution. The switch executes based on the
result of the expression. Depending on the possible outcomes, we define code segments, called as
cases. Each case is a code unit that should be executed when a particular condition is met. All the cases
are listed in body of switch statement. These cases will have the executable statements that will be
executed when the condition is satisfied. Syntax of switch is as follows:
switch(expression)
{
case choice1:
//Statement1;
//Statement2;
break;
case choice2:
//Statement3;
//Statement4;
default:
//Statement5;
//Statement6;
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 55
The break; statement tells the program to leave the switch statement and continue with further
statements immediately after the switch. This can also be used at other places in program other than
switch in X++ coding which will be discussed in looping statements. The default case executes if the
result of the expression does not match any of the cases. Using the default case is optional.
Following is the modified version of above program using switch:
int purchaseAmount = 15000; //Taking int as we are discussing about rounded digits
int discount;
str offer;
switch (purchaseAmount)
{
case 20000 :
discount = 10;
offer = 1+1;
break;
case 15000:
discount = 6;
offer = 1+1;
break;
case 10000:
discount = 5;
offer = ;
break;
case 5000:
discount = 3;
offer = ;
break;
default:
discount = 0;
offer = ;
}
In the above program, we took an expression into switch, which is a single value expression. In
the switch block, we have 5 cases including default case. Both the examples produce same result but,
the first one uses if-else and the second one uses switch. Notice the complexity and the number of lines,
readability of the code. Switch statement makes life simple few times compared with if statement. But,
we should have a set of choices before we proceed to the actual logic.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 56
Lets try to understand a small updated requirement in the above program, where customer
may not buy exactly the same amount as mentioned in all the conditions. Customer may purchase the
amount greater than 5000 and less than 10000 in which he should receive the discount but that will not
be applied programmatically, as the expression will check for exact match available and qualifies for the
execution otherwise, default will be executed. In order to achieve the mentioned discounts, we need an
enhanced selection, where an expression should be given instead of a constant as follows. Please
observe the program carefully and try to evaluate output:
int purchaseAmount = 15000; //Taking int as we are discussing about rounded digits
int discount;
str offer;
switch (true)
{
case purchaseAmount >= 20000 :
discount = 10;
offer = 1+1;
break;
case purchaseAmount >= 15000:
discount = 6;
offer = 1+1;
break;
case purchaseAmount >= 10000:
discount = 5;
offer = ;
break;
case purchaseAmount >= 5000:
discount = 3;
offer = ;
break;
default :
discount = 0;
offer = ;
}
If you observe the modified version, Im using an expression in case statement instead of a
constant, which will be evaluated and the exact match is executed. I leave the understanding of the
program to the readers, as, this kind of evaluation is not possible in all the languages which X++ supports
with a wide range of options. Finally, a small program, illustrating the power of switch in X++, where a
switch statement is allocating multiple results of the expression to one outcome or case. The following
example shows the use of multiple expressions in a switch statement.:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 57
str input = "Mon";
str message;
switch (input)
{
case "red", "yellow", "blue" :
message = "You selected color.";
break;
case "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" :
message = "You selected day.";
break;
default :
message ="Neither colors nor days selected.";
}
info(message);
The same program can be written using if-else, which looks as follows:
str input = "Mon";
str message;
if ((input =="red")|| (input =="yellow")|| (input =="blue"))
{
message = "You selected color.";
}
else
{
if ((input =="Mon")|| (input =="Tue"')|| (input =="Wed") || (input =="Thu")
|| (input =="Fri") || (input =="Sat") || (input =="Sun"))
{
message = "You selected day.";
}
else
{
message ="Neither colors nor days selected.";
}
}
info(message);
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 58
Compare both the programs and try to evaluate which is better based on the lines of code,
complexity and readability and ease of understanding. Also, try the same example removing break and
observe the output. It will execute all the statements which is an odd output which anyone doesnt
expect.
Now that we are clear with the conditional statements, its time to dig more considering a
scenario where you need to calculate the discount amount in the above fashion for 5 of your customers.
For this, it is not feasible solution to write entire code for 5 times as the count may grow or shrink based
on your customers volume. Instead, an operation or the block of statements should be executed for
multiple times. In that case, we use statements called looping statements.
Loops:
Looping statements are used to execute a block of statements until the given condition
evaluates to true. These are also called as repetitive statements or iterative statements as they will
execute multiple times till the condition satisfies and stop when the condition fails. There are 3 types of
loops available in X++ as follows:
While loop
Do while loop
For loop
Following table differentiates each loop with an example:
While loop Do while loop For loop
Syntax while(condition)
{
//Statements;
}
do
{
//Statements;
}while(condition);
for(initialization; condition;
increment/decrement)
{
//Statements;
}
Working Will not enter into loop if
the condition fails for the
first time.
1. Initialize
2. Check condition
3. Execute statements
4. Increment/decrement
5. Go to step 2
Will enter into the loop
even if the condition fails
for the first time.
1. Initialize
2. Execute statements
3. Increment/decrement
4. Check condition
5. Go to step 2
Will not enter into loop if
the condition fails for the
first time.
1. Initialize
2. Check condition
3. Execute statements
4. Increment/decrement
5. Go to step 2
Initialization Initialization of the
looping variable,
Initialization of the looping
variable,
Initialization of the looping
variable,
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 59
increment/decrement is
done separately.
increment/decrement is
done separately.
increment/decrement is
done in for as shown in
syntax.
Execution Will not execute even a
single time if condition
fails.
Will execute once even if
the condition fails.
Will not execute even a
single time if condition fails.
Example 1 int counter = 1;
while(i<=5)
{
info(This is example of
while);
i = i+1;
}
int counter = 1;
do
{
info(This is example of
do-while);
i = i+1;
} while(i<=5);
int counter;
for(i=1;i<=5;i++)
{
info(This is example of
for);
}
Output This is example of while
This is example of while
This is example of while
This is example of while
This is example of while
This is example of do-while
This is example of do-while
This is example of do-while
This is example of do-while
This is example of do-while
This is example of for
This is example of for
This is example of for
This is example of for
This is example of for
Example 2 int counter = 1;
while(i<=0)
{
info(This is example of
while);
i = i+1;
}
int counter = 1;
do
{
info(This is example of
do-while);
i = i+1;
} while(i<=0);
int counter;
for(i=1;i<=0;i++)
{
info(This is example of
for);
}
Output - This is example of do-while -
Difference Condition is verified first
and continues with
execution.
Execute first for once and
later check for condition to
execute loop further.
Condition is verified first and
continues with execution.
From the above table, we can understand a large volume of stuff on how all the looping
statements work in X++. Please note that each loop has its own use and variations like execution of loop,
declaration, increment/decrement options etc. We can use any loop for any requirement but, few are
preferred at certain places due to their nature of work. For e.g. if a block should be executed at least
once regardless of the condition output and check the condition for later executions, we choose do-
while loop.
Following are few more examples that show usage of different looping statements in X++. I
request you to take time, understand and practice the loops and conditional statements as this is the
place where the basic logic of the programmer is built. Lets see a small program which is taught to me
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 60
when I was doing my pre graduation. We will dissect the program to understand how we can think
logically and solve a particular problem. The requirement is, build a program to display * in the
following format:
Before moving to the next paragraph, I request you to think about the above figure and find
your observations. If possible take a pen and paper and write your observations on the formation you
are able to see and proceed with the following text.
From the above figure, my observations are as follows:
Number of * is equals to line number
Whenever I look at this program, I find only one thing, line number 1 has one *, line 5 has 5 *
and so on. So I think, logic is just breaking the problem into parts to solve individual part. Please note
ERP like Microsoft Dynamics AX needs more logic than other standard programming languages because,
here, everything will be there and you have to update the available objects with customizations
required. So, need to understand existing logic and build new logic. Now, lets write the program for the
above formation in X++:
int numberOfLines, numberOfAsterisks;
str asterisks;
for (numberOfLines = 1; numberOfLines <= 5; numberOfLines++)
{
for (numberOfAsterisks = 1; numberOfAsterisks <= numberOfLines; numberOfAsterisks++)
{
asterisks = asterisks + *;
}
info (asterisks);
asterisks = ;
}
The program above may look slightly different to the version of C as one info () statement will
display the string in new line. So, we are populating the required number of asterisks in a single string
and displaying in a single info (), clearing the same string again for reuse, which is a part of logic in X++.
*
**
***
****
*****
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 61
Following is the logic of the program:
Loop the lines first as we plan and start counting the lines first.
For each line, we have to display some asterisks, so, we dont know how many,
but we know that they are equal to line number and the same step should be
performed for multiple times. So write a loop in the first loop, called as nesting
of loops which display asterisks.
But, here info will display one line at a time. If we use info, we will see one
asterisk in one line which will not make us get exact output required. So,
populate the required number of asterisks in a single line and later display the
line.
This is how we achieve the logic for the formation discussed above.
Note: Nesting of loops is writing a loop in another loop, like in the above program, we wrote for in
another for. This is called as nesting. We use nesting if we need to execute a block in another block
which may be done multiple times. For example, lets consider a scenario where we need to display
multiplication tables from 1 through 10. We will see how to write this program using while and for as
follows:
Using while Using for
//Program to display for one number
int number = 10, counter, result;
counter = 1;
while(counter <= 10)
{
result = number * counter;
}
//Program to display for one number
int number = 10, counter, result;
for(counter = 1; counter <= 10; counter++)
{
result = number * counter;
}
In the above program, Im trying to calculate
multiplication table of only one number. So, Ive
used a statement to calculate result, which is
executed 10 times and instead of writing the same
10 times, I used a loop which will execute 10
times. The above program calculates multiplication
table of 10. We can find a modified variant of the
same program below, which will find the
multiplication table from 1 through 10.
There is only one loop like while, but for loop is
used instead of while which reduced a line of code
and improved readability.
int number = 1, counter, result; int number, counter, result;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 62
while(number <= 10)
{
counter = 1;
while(counter <= 10)
{
result = number * counter;
}
}
for (number = 1; number <= 10; number++)
{
for (counter = 1; counter <= 10; counter++)
{
result = number * counter;
}
}
In the above program, we are executing a
statement result = number * counter for 10 times.
So, instead of writing these 10 times, we used a
loop. In the same way, this calculation should be
done for 10 numbers. So, we placed this loop in
another loop, called as nesting of loops.
This is very similar to a while but a nested for is
used instead of while.
Finally, we can also use do-while, a for in a while or a while in a for etc. or some similar logic
based on the requirement.
Continue and Break Statements:
As we are done with most of the part in loops, lets try to understand how to play with loops like
how to stop or how to bypass execution at particular case. We are provided with couple of powerful
keywords, break and continue. As the name indicates, one will terminate the loop immediately when
the statement encounters and the other will bypass execution of particular iteration when the
statement is encountered and continue the execution of loop with the next iteration. Lets consider
couple of scenario, where
A loop should terminate instead of continuous execution when a number is divisible by
some number other than 1 and itself when finding a prime number.
A loop should ignore/bypass execution when a number is even number and proceed
with the next digit when we try to find the primes between 100 and 1000.
Lets check the both examples as follows:
Scenario 1 Scenario 2
int number=23, c;
boolean p = true;
for ( c = 2 ; c <= number/2 ; c++ )
{
if ( number %c == 0 )
int number, c;
boolean p = true;
for(number = 100; number <= 1000; number++)
{
if(number % 2 == 0)
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 63
{
p = false;
break;
}
}
if ( p )
{
info ("Given number is a prime number.");
}
else
{
info ("Given number is not prime number.");
}
{
continue;
}
for ( c = 2 ; c <= number/2 ; c++ )
{
if ( number %c == 0 )
{
p = false;
break;
}
}
if ( p )
{
info ("Given number is a prime number." +
int2str(number));
}
else
{
info ("Given number is not prime number." +
int2str(number));
}
}
In the above example, as p became false, we are
breaking loop which will save our time from
useless iterations as per prime number rule. The
break terminates the loop and come out of loop
whenever it is encountered. In this case, it will not
terminate as 23 is prime. You can check execution
line by line. If you take 25, it will break when c is 5.
In the above example, we are finding whether the
remainder is 0 or not using % operator and if it is
zero, it will bypass execution of the remaining
statements in the loop and jump to increment
statement and continue with next iteration. This
will save lot of time as the logic to find prime will
not be executed for these numbers. In this way,
you can use the continue statement.
Note: int2str () is a function that converts an
integer to string and + is used for concatenation.
We will discuss about more functions in built-in
functions section of this chapter.
**Execute the above program and check the output in infolog.
Some points to note while using break and continue statements:
While using break, we need to place break in proper condition to avoid undesired
results.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 64
Few times, if we miss, continue may lead to infinite looping. So, we need to be cautious
while using continue statement.
Built-in Functions:
Microsoft Dynamics AX has got a large volume of built-in functions that can be used for string
operations, date operations, conversion from one type to another/Type Casting, find some information
like who is user logged, company being used etc. We will discuss few of them in this section. You can
check all the functions available in System Documentation node of AOT.
To access the functions, you can type the function name manually in X++ editor or right click and
select List Built-in Functions in context menu or press Shift + F4. Now, to use the function, click it and
pass the arguments as per the syntax of the function. In the following example, Im using a conversion
function int2str () which converts an integer to string.
int a, b, sum;
a = 10;
b = 20;
sum = a + b;
//info (sum); //This will return error as sum is an integer and info () can accept only strings.
info (int2str (sum)); //Now, integer is converted to string and passed to info.
Lets see another example:
int a, b, sum;
a = 10;
b = 20;
sum = a + b;
info (strfmt (Sum of %1 and %2 is %3., a, b, sum));
The function strfmt () is used for formatting string. The above statement returns output:
Sum of 10 and 20 is 30.
Note that the value of a is displayed in place of %1, value of b replaces %2 and %3 is replaced by
sum. In the above way, you can format the string as per the requirement. You can convert most of the
types to string using strfmt().
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 65
Lets see a small and final example of using string functions:
str subStr(str text, int position, int number);
As the name specifies, this method returns sub string of the text passed into the function. The
function takes 3 arguments and returns a string. Following table explain about each parameter.
Part of function Description
subStr Name of the function.
str Return type of the function.
text The string that is passed to function.
position Indicates the position where the sub string starts.
number Tells how many characters should be taken as sub string.
Example:
str phrase = Every book has its own content.;
info(subStr(letters, 7, 4));
The above info () will display book, starts from seventh character and takes 4 characters as sub
string.
In addition to the above methods discussed, there are a large volume of functions available in
AX, which can be used to do a wide variety of operations. You can always find the relevant function from
AOT while you work and use accordingly.
User Interaction Elements in X++:
While working with any programming language, communicating with user is very important
element. Communication is taking input and displaying output. As from the above programs, we are
always given output assigning values to the variables directly. Now, its time to learn how we can
interact with user in different ways. The following are some of the possible ways to interact with user
directly:
Use Forms, Reports to get input and display output.
Use Info logs, Dialogs and Popup windows.
Creating Forms and Reports have their own significance and are covered in separate chapters. In
the current text, we will see how we can use Info logs, Dialogs and Popup windows.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 66
Infolog:
The infolog is the effective, easiest way to communicate with user to display the output or notify
about something. This can be used to inform the user about how process is executed. Info log can
display multiple messages at a time and we can display different kinds of messages. Following are the
different types of messages that can be displayed in info logs:
Message type Use
Information This is used for displaying general information to
the user.
Warning This is used to display the warning to the user.
Warnings can be used to give information for user
in case of any critical situation.
Error This is used especially to display errors or fatal to
user and grab his attention to take necessary
action.
The following statements shows examples with screen shots on how each message type looks:
info(Invoice
000001 was
posted);
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 67
warning(Timesheet
Periods have not
been created);
error(No rows
have been
migrated);
As we can observe, for each type message, we can see a different kind of image, where the
image indicates the severity of the message, which can also be used to grab the attention of the user.
We can also display multiple messages in info. The following statements produce multiple
statements in tree form in:
info(Info);
warning(warning);
error(error);
In addition to the above, there is setPrefix() function that sets the label for the heading of the
Infolog tree. The label given in the example is, 'setPrefix Example'. The following example shows the
syntax and usage of this method:
setPrefix(setPrefix Example);
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 68
info(Information 1);
info(Information 2);
You can also try the following program which forms infolog tree and check the output:
setPrefix(setPrefix Example);
setPrefix(Sub Header 1);
info(Information 1);
info(Information 2);
setPrefix(Sub Header 2);
info(Information 3);
info(Information 4);
The Box class:
Box class can be used to display a message to application users or take inputs using buttons
from the users. The following is the syntax for Box that displays a simple message on the screen:
Box::info("Text to display.", "Title", "Help text");
The above statement will result the box in the following format.
The above one is a very simple one which displays a message with an OK button. In addition to
the above, there are many box types and each has their own box method. Some of them are as follows:
The following will display a warning:
Box::warning("This is a warning message.", "Title text", "Help text");
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 69
The following will display a Box with Yes and No buttons.
Box::yesNo("Choose Yes or No", DialogButton::Yes, "Yes No Box Example");
If you notice the parameter DialogButton::Yes, which is passed to the function, is used to give
the default button. Though it is not mandatory, it is suggested to give help text, which is removed in this
example.
Lets consider a scenario where you like to judge which block to execute the code or precede the
program based on user input. We will see an example for the scenario mentioned:
DialogButton dialogButton;
dialogButton= Box::yesNo("Press Yes to continue with deletion.", DialogButton::Yes, "Example");
if (dialogButton == DialogButton::Yes)
{
info("You chose to delete record.");
// Code to delete record.
}
else if (dialogButton == DialogButton::No)
{
info("You chose not to delete record.");
}
From the above program, we can understand the use of the Box class and how to handle the
button hits of user. Hope, this makes sense and moving further, lets see how to accept input from user.
The simplest way to provide UI to take input from user is using Dialog boxes.
Dialog boxes are generated from the Dialog class. These are simplified format of forms which
can be used by user to input values. OK and Cancel are the common buttons for all the dialog boxes.
Note that, dialogs are not used in complex scenarios where we have 10+ controls. Most probably, we
use dialogs where we need very few input values that will be in between 4-6 fields. With this
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 70
understanding, lets create a sample dialog box which accepts Name and age of the person and display
them.
static void SimpleDialogJob(Args _args)
{
Dialog dialog;
DialogGroup dialogGroup;
DialogField nameField, ageField;
dialog = new Dialog("Person Dialog");
dialogGroup = dialog.addGroup("Person");
nameField =dialog.addField(Types::String, Name, Name of person);
ageField = dialog.addField(Types::Integer, Age, Age of person);
if (dialog.run())
{
info(strfmt(Name is %1, Age is %2, nameField.value(), ageField.value()));
}
else
{
Info(User cancelled.);
}
}
Lets understand few points from above program:
A dialog is created using Dialog class.
A dialog can be added to group, though it is not mandatory.
Fields in the dialogs are created by using DialogField class. We use addField() method of
dialog object to add the fields. This method returns the reference of DialogField object.
Once the fields are added, we can get the values user entered using dialog field object.
The run() method of dialog is used to display the dialog to the user. If the user click on
OK, this method returns true otherwise, false.
Finally, we can decide which data type the field accepts. We have to pass the type while
adding the fields to the dialog. This can accept Extended Data Types also, which we will
see in coming sections.
Summary
This chapter introduced basic programming concepts of X++. This chapter also covered the input
and output methods. The topics covered include jobs, data types, conditional statements, loops and
types of loops available.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 71
The coverage includes different types of info logs and built-in functions, Box class, which is used
for enhanced user interaction.
This chapter also covered basic logic while programming with X++, which is also applicable to
any other programming languages.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 72
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 73
Object Oriented Programming in X++
As said, X++ is an Object Oriented Programming language, in the current chapter, we will discuss
about the basics of object orientation, how it is supported, what are the object oriented features that
are supported by X++ and advanced features of OOPs.
Teaching or making others understand what Object Oriented Programming is a difficult task
when compared with other topics as majority of the developers who like to learn OOPs are experienced
developers in some procedural programming language and they dont understand what the great
advantages of Object Orientation features are. I suggest the readers of this book to read this section
multiple times if they have any doubt or confusion about Object Orientation.
Earlier days, after programming came into existence and had a good shape, a model came into
existence called Procedural programming languages. In procedural programming, a requirement is
divided into small pieces of programs called as procedures or functions [sometimes referred to as
actions] where each procedure is responsible to execute specific logic of the application. The parts of
programs, often called as procedures or functions comprise of the program and there will be at least
one function in the program and variables and other executable statements will be part of that function.
The best example for procedural language is C. Let us consider a small example of an institution
application in which we have 2 entities to be taken care, students and faculties. You plan to provide
basic functionality where, you like to provide the following operations for both students and faculties,
namely, Create, Read, Update and Delete.
The above said operations require same data, either student or faculty in common to all the
operations. Now, let us consider declaring variables to store data that are operated by functions. We
have to declare variables that are accessible to all those operations. Also, we need to separate logic for
each requirement. We have two options to declare variables in procedural language, either local or
global. This is the only scope available. Now, lets consider declaring the variables as local. As we need
the variables in 4 functions, we need to pass variables to all the functions which will not only consume
more memory but also increases complexity. Lets consider the other chance, declare variables global.
Here, the variables are accessible not only to facultys methods but also students methods, or vice
versa. In this case, variables scope has no access control and the variables are accessible throughout the
application. Declaring variables globally has even more disadvantages. And, separating logic is done
through functions which operate on variables but we cannot specifically write code and manage scope
of functions. Every function is accessible from every other function to call and get the return value. This
is the basic problem that made Object Orientation concept came into existence. The basic challenge in
this approach is scoping and separation of logic, how to define data and how to write the logic. In
addition to the above said points, if the code is developed and needed to extend the functionality
further, in procedural language, modification or redevelopment is required which not only increases
development cost but also test effort will be doubled. With enough issues discussed about procedural
languages, we shall move further into Object Oriented Programming.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 74
Object-oriented programming (OOP) is a programming language model organized around
"objects" rather than "actions". These actions are nothing but the functions/procedures discussed in
the above test and data rather than logic. Previously, a program has been viewed as a logical procedure
that takes input data, processes it, and produces output data.
In Object Oriented Programming, the programming challenge was seen as how to write the
logic, not how to define the data. Object-oriented programming takes the view that what we really care
about are the objects we want to manipulate rather than the logic required to manipulate them. We will
see more about the objects in coming text. In OOP, everything is seen as object. An object is the physical
existence which will be used for data storage as well as execution of the logic.
The first step in OOP is to identify all the objects you want to manipulate and how they relate to
each other known as data modeling. Once you've identified an object, you generalize it as a class of
objects and define the kind of data it contains and any logic sequences that can manipulate the data.
Each distinct logic is known as a method, which contains the actual executable statements. An instance
of a class is called as an object or an instance of a class in some programming languages. The object or
class instance is what we use throughout our program to access the class variables and functions. This
instance provides the storage [called as variable or data] and behavior [called as methods or messages]
that acts on that storage.
Following are the benefits we achieve with Object-oriented programming:
The concept of data classes allows a programmer to create any new data type that is not
already defined in the language itself. The definition of a class is reusable not only by the
program for which it is created but also by other object-oriented programs.
Since a class defines only the data it needs to be concerned with, when an instance (object)
of that class is run, the code will not be able to accidentally access other program data. This
feature, often called as abstraction or data hiding provides greater system security and
avoids unintended data corruption.
The concept of a data class makes it possible to define subclasses of data objects that share
some or all of the main class characteristics. This feature of OOPs is called as inheritance,
this property of OOP forces a more thorough data analysis, reduces development time, and
increase reusability.
While we work with inheritance, a reference of class can behave in multiple forms, which is
used in certain places to exhibit different functionalities. This is called as polymorphism,
which is discussed later in this chapter.
Note: Any object oriented programming covers 3 aspects, Encapsulation, Inheritance and
Polymorphism. These three form the object oriented programming base and are used throughout the
programming. These three are covered extensively with examples in coming topics.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 75
Coming to small history of Object oriented programming languages, Simula was the first
language that came into existence with OOP features. Later, many programming languages came with
OOPs features like C++, Java, C# etc. X++is one of the Object oriented programming languages that
supports development in the ERP Microsoft Dynamics AX. X++ has got all the object oriented features
which makes it a robust programming language. X++ has similarities to C#. X++ is part of the MorphX
development platform that you use to construct accounting and business management systems.
Memory management model of X++ is simple. Objects are created with a new operator. There are no
explicit programmer-defined pointer data types, and there is no pointer arithmetic.
X++ classes are divided into 2 types, System classes and Application classes. System classes for a
broad range of system programming areas in which few are as follows:
Reflection on classes and tables.
Collections of objects.
File input and output.
Manipulation of user interface items such as forms and reports.
XML building and parsing.
Microsoft Dynamics AX application classes provides management of many types of business
processes. We can also design and develop new classes in Microsoft Dynamics AX, custom built classes
as per developer requirement. This section will cover custom built classes i.e. programming and using
new classes to maximum extent with all the features. Other 2 types of classes [System and Application
classes] will be covered in sub sequent chapters.
Note: Microsoft Dynamics AX supports interoperability between classes written in X++ and in C#
or other .NET Framework languages which can be used at times when we need to use the features of
those by adding reference of the .dll files to AX.
With enough explanation, lets develop a sample class to understand the basics of OOP in X++.
Classes
A class is collection of variables and methods. In AX, a class can be created in following ways:
Using the class wizard.
Directly from classes node in AOT.
To create a class using the class wizard, go to Tools > Wizards > Class Wizard. You will get the
wizard screen. The following screen is used for providing few basic values while creating a class:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 76
In the above screen, we have to give a class name, the basic thing which we need to identify a
class. You can select the class template if any available or create a new one. Class templates can be used
to create new classes that extends/implements classes/interfaces on the fly without much effort. You
can also select the class to inherit and click on Next. In the coming screen, we can select interfaces that
should be implemented by the class. Please note that we can inherit only one class but can implement
any number of interfaces. We will discuss about this variation further in coming text. Once the selection
is done, we can select the methods that should be auto created and finish. Youll see a new class created
in AOT. You can open the class and update/add the implementation to methods as per the requirement.
The second way to create a class is, open AOT, right click on Classes node and click New > Class.
Youll see a new class created with a class name ClassX, X being the digit e.g. Class1. Now, expand the
newly created class or right click on class and click on View Code. You will see the following declaration:
public class Class1
{
}
Note that the above section is called as Class Declaration. You can change the name of the class
by changing in the above class declaration section or from properties of the class. Now, lets name the
class as Calculator, i.e. we are going to develop a calculator class and understanding the working of
classes. Once you change the name, the class looks as follows:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 77
As you can see in the above figure, the class name is changed to Calculator. While writing this,
Ive changed this from properties window. You can change from classDeclaration as discussed
previously. Just remove class name and type the name you need in the classDeclaration section in code.
Now, lets understand functionality of Calculator. My requirement is, design a simple calculator
which performs arithmetic operations. For this, I need 3 variables to store values and result, 4 methods
which are used to do operations on the variables. To declare the variables, just add variables declaration
in classDeclaration as in the following figure:
Once you add/modify the code, compile to check if there are any errors so that you can update
immediately, or we have to mess up with errors all at once. As we have declared the variables, we will
add few methods [note that methods are functions in class. We use the term methods instead of
functions in object oriented programming]. You can find new button, which is highlighted in box in the
image above. Click on that button and you will get a new method added to your list below
classDeclaration.
Now, lets understand and modify the method declaration as per our requirement. The
declaration has the following parts:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 78
method1():name of the method.
void, which says that method dont return anything.
private, says that the method is used only in this class and cannot be accessed outside of
the class. This is called as access modifier. We have few other access modifiers which
will be discussed immediately after this example.
Lets change this declaration as follows:
public void parmNumber1(int _number1)
{
number1 = _number1;
}
public void parmNumber2(int _number2)
{
number2 = _number2;
}
The above methods are declared using public modifier. This modifier gives accessing of this
method by any other code snippet i.e. this method is accessible from Jobs, other classes and from any
other place. These methods are called as parm methods, which are usually used for setting and getting
values of particular variable. The methods declared here are used for accessing number1 and number2,
which are declared in classDeclaration. We are passing one parameter. When we call this method, we
have to pass an integer argument to that method. These methods set the values to the numbers. Now,
we will add few more methods, as follows:
public int add2numbers()
{
result = number1 + number2;
return result;
}
public int sub2numbers()
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 79
result = number1 - number2;
return result;
}
public int mul2numbers()
{
result = number1 * number2;
return result;
}
public int div2numbers()
{
result = number1 / number2;
return result;
}
In all the above methods, we are not accepting any parameters but returning an int value from
all the methods. This is how we can return values from called method to calling method.
The previous figure shows the class after you add all the methods. Now that we have done
adding variables, methods to our class, we will try to use what we have developed. Once we are able to
access the components we have developed, we can move to the next phase of development and
understand advanced stuff of OOPs in X++.
To use a class declared, we have to create an object of a class. Object of class is also called as
instance. We will write a job to use the class as follows:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 80
Full code for the job is as follows:
static void ObjectDemo(Args _args)
{
Calculator calculatorObject;
calculatorObject = new Calculator();
calculatorObject.parmNumber1(20);
calculatorObject.parmNumber2(10);
info(strFmt("Addition is : %1", calculatorObject.add2numbers()));
info(strFmt("Subtraction is : %1", calculatorObject.sub2numbers()));
info(strFmt("Multiplication is : %1", calculatorObject.mul2numbers()));
info(strFmt("Division is : %1", calculatorObject.div2numbers()));
}
This job is calling the methods that are provided in calculator by setting the number1 and
number2 using the parm methods and displaying the result that is returned by the basic arithmetic done
in the methods implemented. The above job will display an info box with the following output:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 81
Lets try to understand the parts of program. In order to use the class, first we need to create an
object of the class. The following is the way how we created an object of the class:
Calculator calculatorObject;
In the above statement, we are creating a variable of type Calculator. This variable is called as
reference. This reference cannot be used until we physically allocate memory for the object. We call this
as instantiation of object. Until we create an instance/object, we cannot use the class variable/reference
of the class. Note that, if you try to access/use members of class without instantiating, you will get an
exception that you are trying to access members without creating object.
To create the object, we use the keyword new. When you use keyword new, memory is
allocated for the object. This is called as physical existence and hence forth, you can use the object to
call/use the methods declared in the class.
calculatorObject = new Calculator();
You can create any number of objects for a single class. This is like opening a multiple
documents in a single application like a PDF reader. Let us suppose we need 2 objects, the objects are
created as follows:
calculatorObject1 = new Calculator ();
calculatorObject2 = new Calculator();
and, memory allocation is different for 2 instances and looks as follows:
calculatorObject2
number1
number2
result
calculatorObject1
number1
number2
result
parmNumber1()
parmNumber2()
add2numbers()
.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 82
When an object is created, a memory block is created to store all the variables in the object.
Each object is allocated memory separately for storing its own copy of variables. When you use the
object to call the methods, the variables that are specific to the object are used while operating the
variables. For example, when we call calculatorObject1.add2numbers(), this method will use the
variables of calculatorObject1 and vice versa.
Now, lets try to understand the scope of members of a class. The following scoping is available
for members of a class:
A member is accessible to any of the methods inside or outside of the class.
A member is accessible to only other members of the same class.
A member is accessible to the other members of same class and members of its
child classes.
A member is accessible without creating an object.
The above are the types of accessing levels we can provide in X++. The following table describes
about the accessing levels and the various other properties of each type:
Keyword Usage
Private These members are accessible only through other members of same class.
Public These members are accessible from anywhere in AX, provided, we have to create an
instance and call these members using that object.
Protected These members are accessible from the members of the same class and its child
classes but not from outsiders. We will see more about the child classes in coming
sections and discuss more about this.
Static These members can be called without creating object, directly using the class name.
There are few rules associated with static members which will be discussed.
The members in above table are only applicable for methods and not variables. All variables in
X++ classes are scoped in such a way that they can be used in methods of same class and its child
classes. Static access modifier is only applicable to methods and not variables. This seems different
compared to other object oriented programming languages but, we will find the advantages once we
start using X++. Its time to move to some more stuff about the classes. We will modify the above class
to have private and public methods as follows:
public class Calculator
{
int number1, number2, result;
}
public void parmNumber1(int _number1)
{
number1 = _number1;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 83
}
public void parmNumber2(int _number2)
{
number2 = _number2;
}
private void add2numbers()
{
result = number1 + number2;
}
private void sub2numbers()
{
result = number1 - number2;
}
private void mul2numbers()
{
result = number1 * number2;
}
private void div2numbers()
{
result = number1 / number2;
}
public int getResult(str _operation)
{
switch(_operation)
{
case "Add":
this.add2numbers();
break;
case "Sub":
this.sub2numbers();
break;
case "Mul":
this.mul2numbers();
break;
case "Div":
this.div2numbers();
break;
}
return result;
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 84
In the above program, we updated the methods that has business logic with private modifier.
These can be called only from the same class. As you can see, the methods are called in getResult() of
the same class. Here, the methods are declared private and may only be called from methods in class
Calculator. The following image shows the list of methods that are accessible:
From the above image, you can observe that when we use .(dot) operator, intellisense identifies
the private methods and is not displaying. If we still try to access the methods, we will get an error like,
The method is declared private and may only be called from methods in class Calculator. from X++
compiler. Private methods are very similar to other methods except with the said difference.
Note: A class can have any number of private/public/protected/static methods based on requirement.
If we observe the program, we are not modifying the values of number1 and number2 once
initialized but we are calling 2 methods for this initialization task. This time, we will see how to initialize
while creating object. We will do this by using special method called as constructor. We create a
constructor in X++ by implementing a special method called as new() as follows:
public void new(int _number1, int _number2)
{
number1 = _number1;
number2 = _number2;
}
We can write this method in 2 ways, by coding directly what we needed as above or right click
on class, click on Override method [we will discuss overriding of methods in coming text], and click new.
This will add blank new method to your class. You can implement the code you need in this method.
Now, while creating the object, we have to call the constructor in the following way:
calculatorObject = new Calculator(10, 20);
The above statement will call new() method automatically, which will set the values without
calling the parm methods. Note that, constructor is called only once when an object is created. For every
object, constructor is called and this is the first method called on an object. In addition to new, we can
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 85
find one more method in override methods, finalize().This method terminates the object. It is never
used within the application and exists only for convention.
Note: Usually, constructors are used for initialization purposes, like initialization of variables etc.
Now we know the following OOPs concepts in X++:
Classes.
Objects.
Access modifiers.
Constructors and Destructors.
Memory allocation.
Lets try to understand OOPs terminology, Encapsulation and Abstraction.
Encapsulation is binding data and members into a single entity i.e. the variables and methods
are bound into a single instance and are accessed using that instance. This is achieved using concept
called as class. A class will bind the variables and methods into a single entity which is encapsulation. We
use this class by creating the object. The class is also called as template of object.
Abstraction is hiding implementation of member which is unnecessary to programmers or
outside of class i.e. the implementation details that are not necessary for programmers outside of that
class can be hidden in order to avoid accessing of those implementations unexpectedly or accidentally.
The best example of abstraction is, in the above program we are setting the values to number1 and
number2 using constructor and get the result of the desired operation using the getResult() method.
Now that, it is not required for programmer to access or find implementation details of the methods
add2numbers() or any other methods from outside of class until and unless we get some problem with
the method. So, we are hiding the implementation and this is done using the private access modifier as
this modifier will not allow the member to access outside of class.
Now we are clear what is encapsulation and abstraction, its time to go further to next step,
Inheritance.
Inheritance
Writing classes is the first part of Object Oriented Programming. Simply Classes, though provide
many features, we need still more updates from any programming language. One of them is, reusability.
Once we design a class, we need a facility to reuse that. Let us consider a scenario where, we need a
more advanced calculator, the scientific calculator. As we know, every scientific calculator will do
ordinary calculator operations like addition, multiplication etc. and also few extra operations like
Exponent, Factorial etc.
For this kind of scenarios, we have 2 options, one being implementing scientific calculator from
scratch including features of standard calculator. This has disadvantages like, development and test
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 86
effort increases. Development of standard calculator features should be redeveloped and tested. Option
2 will be, use the standard calculator and extend its features. The advantages using this option are,
reduced effort which will save time, reduce development and test effort and cost. This option of
extending the existing functionality is done using Inheritance.
We will see the definition of Inheritance:
Inheritance is extending or deriving one class from another. The class which will extend another
class is called as sub class or child class and which is extended is called as parent class or super class or
base class.
Extending means, deriving the functionality i.e. members and methods of one class into
another. When we extend one class in another, it means that we can use the functionality of parent
class in child class, extend the functionality of parent class in child class or even modify the existing
behavior of parent class in child class. We will see each of the features in detail in coming text.
Let us try to create scientific calculator in 2 ways:
Option 1: Create from scratch Option 2: Extend ordinary calculator
public class ScientificCalculator
{
int number1, number2, result;
}
public void parmNumber1(int _number1)
{
number1 = _number1;
}
public void parmNumber2(int _number2)
{
number2 = _number2;
}
public void add2numbers()
{
result = number1 + number2;
}
public void sub2numbers()
{
result = number1 - number2;
}
public void mul2numbers()
{
result = number1 * number2;
}
public void div2numbers()
{
result = number1 / number2;
}
public class ScientificCalculator extends Calculator
{
}
public int exponent(int _value, int _exp)
{
int expResult = _value, counter;
for(counter = 1; counter <= _exp; counter++)
{
expResult = expResult * 10;
}
return expResult;
}
public int factorial(int _number)
{
int factResult = 1, counter;
for(counter = 2; counter <= _number; counter++)
{
factResult = factResult * counter;
}
return factResult;
}
public int square(int _number)
{
return _number * _number;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 87
public int getResult()
{
return result;
}
public int exponent(int _value, int _exp)
{
int expResult = _value, counter;
for(counter = 1; counter <= _exp; counter++)
{
expResult = expResult * 10;
}
return expResult;
}
public int factorial(int _number)
{
int factResult = 1, counter;
for(counter = 2; counter <= _number; counter++)
{
factResult = factResult * counter;
}
return factResult;
}
public int square(int _number)
{
return (_number * _number);
}
}
From the above table, we can observe the difference between 2 options, where, option 1
doubles the code, effort and cost where option 2 reduces great effort with only one keyword,
extends. This keyword makes our life easier. We will try to understand what happens in both cases:
Option 1: We are redeveloping everything i.e. coding is done for all methods again. This will
initiate dev and test effort, which will increase the cost of development.
Option 2: We are extending the functionality of existing class which is tested. Here, we
add/modify only the features which are required in the class 2, often called as child class. This will
reduce the development and test effort thus, reduce development cost.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 88
The following figure explain about how this is done:
Though we are developing a new class called ScientificCalculator in second case, this extends the
functionality of first class, Calculator. When we create an object for ScientificCalculator in second case,
this will have an instance of Calculator internally, which works as follows:
Case 1 Case 2
Calculator cob = new Calculator();
ScientificCalculator scob = new
ScientificCalculator();
In this case, every object is individual which will
provide its own services.
ScientificCalculator scob = new
ScientificCalculator();
In this case, when we create an object of child
class, a parent class object is created internally and
can be accessed using the child object. i.e. all the
parent properties are inherited into child.
Following are few notable points while working with inheritance in X++:
X++ supports single and multilevel inheritance. Though multiple is supported, this is
using interfaces which will be discussed later in this text.
When we inherit a parent into child, all the properties from parent class are derived into
child. Please note that deriving will be considering access modifiers.
public class ScientificCalculator
public void parmNumber1(int _number1)
public void parmNumber2(int _number2)
public void add2numbers()
public void sub2numbers()
public void mul2numbers()
public void div2numbers()
public int getResult()
public int exponent(int _value, int _exp)
public int factorial(int _number)
public int square(int _number)
public class ScientificCalculator
public int exponent(int _value, int _exp)
public int factorial(int _number)
public int square(int _number)
public class Calculator
public void parmNumber1(int _number1)
public void parmNumber2(int _number2)
public void add2numbers()
public void sub2numbers()
public void mul2numbers()
public void div2numbers()
public int getResult()
cob scob
scob
cob
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 89
We can create a child class object as well as parent class object. When we create a
parent object, this can be used to access only properties of parent and a child copy will
not be created. Whereas, when we create an object of child class, an instance of parent
class is also created internally as shown in the above figure and we can use the same
object to for the parent class properties also, based on access modifiers.
Now, we will create a job to create an instance of ScientificCalculator and understand how the
properties can be accessed:
We will try to understand what happens when we create an object of Calculator and
ScientificCalculator. When we create an object of Calculator, we can access all the methods and
members of Calculator form anywhere, based on access modifiers rules applied. When we create an
object of type ScientificCalculator, it will create an internal object of type Calculator, thus, enables us to
access members/methods of ScientificCalculator and Calculator also as in the above example. As you
can see in the above example, we are able to access add2numbers() and getResult() of Calculator and
exponent() of ScientificCalculator classes using a single object of type ScientificCalculator. This way, we
can achieve reusability using inheritance, by simply extending/inheriting one class into another, we can
access the features of parent class in child class, which reduces the development cost and gives us many
extra capabilities which will be discussed in further text.
Access Modifiers Revisited:
As we know about the access modifiers, private, public and protected, we will try to understand
about these in detail while using inheritance:
Keyword Usage
Private These members are accessible only through other members of same class. Even child
class methods cannot access these private methods.
Public These members are accessible from anywhere in AX, provided, we have to create an
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 90
instance and call these members using that object. Child classes can access these
methods without creating object, but, we will use this operator to access those
methods.
Protected These members are accessible from the members of the same class and its child
classes but not from outsiders. Child classes can access these methods without
creating object, but, we will use this operator to access those methods.
Lets write a small program and understand these modifiers practically. The following program
will make you clear in using access modifiers. You can work on this example multiple times to get clear
picture on how and what members can be accessed directly and with object etc., in child classes and
other code segments like jobs, table methods and other classes etc.
class AccessModifiers
{
int number1;
}
private void privateMethod()
{
info("This method is declared using private access modifier");
}
public void publicMethod()
{
info("This method is declared using public access modifier");
}
protected void protectedMethod()
{
info("This method is declared using protected access modifier");
}
class InheritModifiersDemo extends AccessModifiers
{
int number1;
}
public void callParentMethods()
{
number1 = 25;
info("This method is child class method using private access modifier");
this.protectedMethod();
this.publicMethod();
//this.privateMethod(); Illegal as per private access modifier rule and will throw error.
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 91
static void accessModifierDemo(Args _args)
{
AccessModifiers accessModifiers = new AccessModifiers();
InheritModifiersDemo InheritModifiersDemo = new InheritModifiersDemo();
accessModifiers.publicMethod();
//accessModifiers.protectedMethod(); This will throw error as this cannot be accessed outside of
the class and its child.
//accessModifiers.privateMethod(); This throws error as this cannot be accessed outside of the
class.
//accessModifiers.number1 = 25; This statement is not valid as the variables are not accessible
outside of the class and its child classes
inheritModifiersDemo.callParentMethods();
inheritModifiersDemo.publicMethod();
}
The following points can be noted from the above programs sequence:
The variables declared in class can be used throughout the class and its child classes
directly referring the name of the variable. The operator this is not required and is not
used to access the variable.
We can only call the parent class methods which are public and protected from child
classes. Note that, when we call parent methods, we have to use this operator to refer
the parent class methods and it is not required to create object to call the methods.
If we try to refer the private methods of parent class from child class methods or the
private and protected methods outside of the scope, we get the following errors:
The method is declared private and may only be called from methods in class AccessModifiers.
The method is declared protected and may only be called from methods in classes derived from
AccessModifiers.
Note that, we cannot use the variables out of the class and its child classes either directly or by
using the object.
Till now, we have seen what is inheritance and various variations with the access modifiers.
Now, we will understand and use the different types of inheritance.
As said in previous statements, in starting of inheritance text, we have various types of
inheritance as shown in following table:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 92
Single/Simple Inheritance
Multilevel Inheritance
Hierarchical Inheritance
Multiple Inheritance
Hybrid Inheritance
From the above figure, we can see and understand the various types of inheritance possible.
But, only few are supported by X++ completely. We will understand the types clearly:
In Simple Inheritance, only one class is derived from another class. This is the simplest
form of inheritance and is also called as single inheritance. This is possible in any
programming language that supports Inheritance
Multilevel Inheritance is extending the levels of inheritance by extending a child class
into another child class. As from the figure, we can observe that class B inherits class A
where again, class C extends class B. Here, A is called parent class, B is child class and C
A
B
A
B
C
A
B C D
A
B C
D
A B
C
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 93
is grandchild. Terminology may change like this also, B is child for A and parent for B. C is
child of B and grandchild of A. A is parent of B and grandparent of C etc. This kind of
inheritance is possible in X++. A sample example scenario will be, let us consider the
requirement of a trigonometric calculator where the calculator should include the
features of ordinary calculator and scientific calculator. Here, we have 2 options again.
One, develop trigonometric class extending ordinary and scientific. But as scientific
includes ordinary calculator features, it is sufficient for us to extend the scientific
calculator which will include ordinary calculator features in it. The development will be
as follows:
Ordinary Calculator -> Scientific Calculator -> Trigonometric Calculator.
We will see how to write a program and call the methods as follows. Please note that this is a
sample program to simulate very few operations of trigonometric functions and is not a full-fledged
calculator:
class TrignometricCalculator extends ScientificCalculator
{
}
public int cosine(int _angle)
{
switch(_angle)
{
case90:
return0;
case0:
return1;
default:
return -1;
}
}
public int sine(int _angle)
{
switch(_angle)
{
case90:
return1;
case0:
return0;
default:
return -1;
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 94
}
public void new()
{
}
static void ObjectDemo(Args _args)
{
TrignometricCalculator calculatorObject;
calculatorObject = new TrignometricCalculator();
calculatorObject.parmNumber1(20);
calculatorObject.parmNumber2(10);
calculatorObject.add2numbers();
info(strFmt("Addition is : %1", calculatorObject.getResult()));
info(strFmt("Exponent is : %1", calculatorObject.exponent(3, 4)));
info(strFmt("Sine of angle is : %1", calculatorObject.sine(90)));
}
Output:
In the above job, we created an object of type TrignometricCalculator, but calling the methods
of parent classes also. Note that, we are following access modifier rules when calling the methods as
follows:
calculatorObject.add2numbers();
info(strFmt("Addition is : %1", calculatorObject.getResult()));
info(strFmt("Exponent is : %1", calculatorObject.exponent(3, 4)));
info(strFmt("Sine of angle is : %1", calculatorObject.sine(90)));
Please note that the methods in base class are followed from last example, as follows:
Hierarchical inheritance is, deriving multiple child classes from a single parent class. A
scenario will be, let us assume a parent class called Shapes from which 3 child classes
are derived namely, Rectangle, Triangle and Square. The example will be as follows:
class Shapes
{
}
public void output()
{
public class Calculator
{
int number1, number2, result;
}
public void add2numbers()
{
result = number1 + number2;
}
public void div2numbers()
{
result = number1 / number2;
}
public void mul2numbers()
{
result = number1 * number2;
}
public void sub2numbers()
{
result = number1 - number2;
}
public void new(int _number1, int _number2)
{
number1 = _number1;
number2 = _number2;
}
public void parmNumber1(int _number1)
{
number1 = _number1;
}
public void parmNumber2(int _number2)
{
number2 = _number2;
}
public int getResult()
{
return result;
}
public class ScientificCalculator extends Calculator
{
}
public int exponent(int _value, int _exp)
{
int expResult = _value, counter;
for(counter = 1; counter <= _exp; counter++)
{
expResult = expResult * 10;
}
return expResult;
}
public int factorial(int _number)
{
int factResult = 1, counter;
for(counter = 2; counter <= _number; counter++)
{
factResult = factResult * counter;
}
return factResult;
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 96
info("This is shape.");
}
class Rectangle extends Shapes
{
}
public void areaOfRectangle()
{
info("Area of Rectangle.");
}
class Triangle extends Shapes
{
}
public void areaOfTriangle()
{
info("Area of triangle.");
}
class Square extends Shapes
{
}
public void areaOfSquare()
{
info("Area of square.");
}
All the classes in the above type of inheritance will share a method called output() which is
available in any class as all the child classes are inheriting only one parent class.
Multiple and Hybrid inheritance are not possible in X++ directly i.e. we cannot extend
two parent classes for a single child class. Anyhow, we can simulate this by using
interfaces. We will see how to do once we are done with interfaces.
Before moving to the next step, lets try to understand what abstract methods are. Let us consider
a scenario where we dont know implementation of few methods and know implementation of few
methods. In that case, there are 2 possibilities, one, implement what are known and complete the class.
Second is, implement all the methods known and declare all other methods as abstract which means
that the methods are not implemented.
An abstract method is a method which dont have any implementation and declared using abstract
modifier. This will have just { and a }. These methods are declared in a class and this class is called as
abstract class. Abstract class will have the following properties:
An abstract class is declared using abstract keyword.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 97
An abstract class may have one or more abstract method and one or more concrete
methods [concrete method is a method that has implementation].
An abstract class cannot be instantiated i.e. objects cannot be created for abstract class.
You may wonder how to use the abstract class. Just inherit this class into another, implement all
the methods and use the class. This is the way we use an abstract class. Lets see this using an example:
The job is as follows:
static void abstractDemo(Args _args)
{
//AbstractClass abstractDemoObject = new AbstractClass(); This is not possible, as there is no
implementation for few methods
OneMoreClass abstractDemoObject = new OneMoreClass();
abstractDemoObject.abstractMethodOne();
abstractDemoObject.abstractMethodTwo();
abstractDemoObject.concreteMethod();
abstractDemoObject.ConcreteOne();
abstractDemoObject.ConcreteTwo();
}
Note that, if we try to create an instance of abstract class, we get the error, The class
OneMoreClass must implement method abstractMethodOne.. As there is no implementation for the
methods in abstract class, if an instance is allowed to be created, we will somehow call the abstract
abstract class AbstractClass
{
}
public void ConcreteOne()
{
info("ConcreteOne() of AbstractClass");
}
public void ConcreteTwo()
{
info("ConcreteTwo() of AbstractClass");
}
abstract public void abstractMethodOne()
{
}
abstract public void abstractMethodTwo()
{
}
class OneMoreClass extends AbstractClass
{
}
public void concreteMethod()
{
info("Concrete Method");
}
public void abstractMethodOne()
{
info("Implemented abstractMethodOne");
}
public void abstractMethodTwo()
{
info("Implemented abstractMethodTwo");
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 98
method accidentally or forcibly to check which may not find the implementation and may throw run
time exception. To avoid this unexpected result, we are restricted to create an instance for abstract
method.
Output of the above program looks as follows:
Note: We can access the methods of abstract class from child class once we implement all the
methods using the object of child class. When we call the abstract method, the implementation which is
there in child class is called. If we dont implement at least one method, the child class will also become
abstract class and should be declared abstract. We can declare abstract any number of levels but cannot
instantiate until we implement all the methods in some class at some level.
Writing of the parent method in child is called as overriding. We will see what is overriding in
the coming section.
Overloading and Overriding
Overloading and Overriding is done on methods and variables in classes. The following
statements describe about overloading and overriding:
Overloading of method is writing the same method with different number or different types of
arguments in the same class. The method name will be same but argument types or number of
arguments will be different. This overloading is supported by most of object oriented languages except
X++. X++ doesnt support method overloading and cannot be used in AX.
Overriding is writing a method implemented in parent class again in child class. Note that, the
parent class method is again implemented in child class. So, the method signature remains same and will
not change. If we try to change the number of arguments or type of arguments in child class, we will get
unexpected results.
Why do we override methods?
Let us suppose that there is a scenario where we like to provide enhanced implementation or
extend the implementation provided by base class in child class. In this case, we have to rewrite the
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 99
method of parent in child and may write new code. This behavior of method is called overriding of
method.
We can override a method in 2 ways, one writing the code manually and second, right click on
child class and select Override method as in following figure:
As in the above figure, we can override from the methods displayed in the section.
An example will clear our doubts as follows:
class Shapes
{
}
public void area()
{
info("Area of Shape is unknown.");
}
class Square extends Shapes
{
}
public void area()
{
info("Area of square is: l*l");
}
class Rectangle extends Shapes
{
}
public void area()
{
info("Area of rectangle is: l*b");
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 100
}
static void OverrideSample(Args _args)
{
Shapes shapes = new Shapes();
Rectangle rectangle = new Rectangle();
Square square = new Square();
shapes.area();
rectangle.area();
square.area();
}
Output will be as seen in the following image:
In the above program, we are overriding method called as area for which we dont know
implementation for Shape. This method has a meaningful implementation in Rectangle and Square and
thats where we are providing a new implementation for them in child classes. Overriding of methods is
not only restricted to other methods, you can override constructors and destructors i.e. new() and
finalize() also. This will call the new() in child class to parent class sequence. For example, let us suppose
that there is new() in Shapes class and Square class, where Square class is a child of Shapes class and
note that super() should be there in new() of Square class i.e. child class. When you create an object of
type Square, this will call new() of Square as well as Shapes in the order new() of Square -> new() of
Shapes i.e. constructor of child class will trig constructor of parent class.
If we like to extend the functionality of parent class method, we have to execute the base class
method and then write our statements. For doing this, we use a keyword called super(). The following
example will show you how super() works:
class Shapes
{
}
public void area()
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 101
info("Area of Shapes");
}
class Rectangle extends Shapes
{
}
public void area()
{
super();
info("Area of Rectangle");
}
static void OverrideSample(Args _args)
{
Shapes shapes = new Shapes();
Rectangle rectangle = new Rectangle();
shapes.area();
rectangle.area();
}
Output looks as follows:
We are able to see the statement Area of Shapes twice as super() call in area() of Rectangle
will call the area() of shapes which will display the line again. This will make us to extend/call the
functionality of the parent class. Finally, super() can be called at any place in method after declaration
and even accept return values if the parent class method return a value as follows:
public int area()
{
int ret;
ret = super();
info("Area of Rectangle");
return ret;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 102
}
You may ask me, Why dont you write the method area() in Shapes class, an abstract
method?.
You are perfectly right. In this case, we can write the method as abstract as there is no
meaningful implementation for area of shape until we know what the shape is. But, for time being, as I
dont have any other methods, I implemented the method though it is not mandatory to implement
that.
Now, let us suppose, we dont know implementation of any of the methods in a class. What do
we do? The answer is writing all methods as abstract in an abstract class so that we will implement in
child class and can be used. But, we have a big problem, only one class can be extended by another class
i.e. we can inherit only one class at a time. So, what is the option?
Interface would solve our problem. An Interface is a collection of all the abstract methods.
Interface will not have any concrete methods. It is just a collection of abstract methods. A small question
again in your list of queries, what is the requirement of writing some code when we dont have
implementation for any methods? The answer is, to give specifications. Yes, if you like to say that
particular methods/part should be implemented to say that the program/application is complete, we
can provide interface and upon complete implementation of interface, we say that the application is
implemented. We will see a sample program as follows:
interface Shapes
{
}
public void area()
{
}
class Square implements Shapes
{
}
public void area()
{
info("Area of square is: l*l");
}
class Rectangle implements Shapes
{
}
public void area()
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 103
{
info("Area of rectangle is: l*b");
}
static void OverrideSample(Args _args)
{
Rectangle rectangle = new Rectangle();
Square square = new Square();
rectangle.area();
square.area();
}
Following are few points to note while using interfaces:
We have to use a keyword called interface instead of class while declaring the interface.
We have to use implements keyword instead of extends while implementing the
interfaces.
Interfaces cannot be instantiated as there is no implementation for methods and the
error we get if we try is:
Interfaces may not be instantiated.
When we try to implement an interface, all the methods should be implemented. Partial
implementation of interface will make the class an abstract class.
Hope, this clear the doubts with interfaces.
Now, lets move to a scenario, where we can simulate multiple inheritance as follows:
class Rectangle extends Calculator implements Shapes
{
}
class Rectangle extends Calculator implements Shapes, Line
{
}
We can extend one class but can implement any number of interfaces. Note that, we have to
implement all the methods in all interfaces otherwise, the class will become abstract ad should be
declared abstract or we get errors.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 104
Polymorphism
Most of the developers who learn OOPs for the first time feel Classes and Inheritance pretty
easy, feel good and think that this is all Object Orientation is. If you feel the same and skip the topic, you
lose a lot. There is a core part that you miss. Lets see a small example as follows:
class PolyParent
{
}
public void PolyMethod()
{
info("PolyMethod () of PolyDemo");
}
class PolyChild extends PolyParent
{
}
public void PolyMethod()
{
info("PolyMethod() of PolyChild");
}
public void ChildMethod()
{
info("ChildMethod() of PolyChild");
}
class PolyOtherChild extends PolyChild
{
}
public void PolyMethod()
{
info("PolyMethod() of OtherChild");
}
public void OtherChildMethod()
{
info("ChildMethod() of PolyChild");
}
static void PolyDemo(Args _args)
{
PolyParent polyParent = new PolyParent();
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 105
PolyChild polyChild = new PolyChild();
PolyOtherChild polyOtherChild = new PolyOtherChild();
polyParent.PolyMethod();
polyParent = new PolyChild();
polyParent.PolyMethod();
polyParent = new PolyOtherChild();
polyParent.PolyMethod();
}
Output looks as follows:
The following is the structure of each class:
PolyParent PolyChild PolyOtherChild
PolyMethod() PolyMethod()
ChildMethod()
PolyMethod()
OtherChildMethod()
As you can see, we are using one reference of PolyParent to instantiate 3 types of objects,
namely, PolyParent, PolyChild and PolyOtherChild. This kind of using one in many forms is called as
Polymorphism. We will try to understand few other points as follows:
A parent reference can be used to instantiate child object. When we do this, we can call
the methods of parent class which are overridden in child class.
When we call the methods that are overridden using parent reference with child
instance, the child methods gets called instead of parent. This is called as Dynamic
Method Dispatch or Runtime polymorphism.
When we instantiate child class object using parent class reference, we cannot call the
methods of child which are not there in parent as parent reference doesnt know about
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 106
those methods. Only the methods which have signature or implementation in parent
class can be called in child provided, they should be overridden.
This polymorphism functionality can also be used with Abstract classes and Interfaces
also.
There are 2 more keywords used on methods, namely, final and static. Lets understand using
the following program:
final class FinalClass implements Intf
{
}
final public void FinalMethod()
{
info("FinalMethod() of FinalClass");
}
static public void StaticMethod()
{
info("This is a static method.");
}
static void PolyDemo(Args _args)
{
FinalClass::StaticMethod();
}
The keyword final is used to restrict a class being inherited into child class i.e. a final class cannot
have child classes and if we try, we get the error Final class <Class name> may not be extended. In the
same way, to avoid method being overridden in child class, we use final keyword in front of method
declaration as shown in the above example.
In some cases, we like to provide accessing of method without creating an object for the class.
To provide this kind of accessing of methods outside of class, we use static modifier. Static methods are
accessed directly using the class name as shown in the above example using :: operator. There are few
rules associated with static methods as follows:
Static methods are accessed directly and cannot be accessed using the objects of a class.
A static method can refer only static context. Non static context should be referred
using the object i.e. members or methods that are non-static cannot be used directly in
static methods.
We cannot use this keyword in static methods as these are not associated with any
object.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 107
Im writing a couple of programs and leaving understanding to you. I request you to go through
the programs multiple times if you dont understand or mail me to the given mail id for further
explanation:
Sample 1 Sample 2
interface Intf
{
}
public void implementMethod1()
{
}
class Poly1 implements Intf
{
}
public void implementMethod1()
{
info("Implementation of implementMethod1 in
Poly1");
}
class Poly2 implements Intf
{
}
public void implementMethod1()
{
info("Implementation of implementMethod1 in
Poly2");
}
class Poly3 implements Intf
{
}
public void implementMethod1()
{
info("Implementation of implementMethod1 in
Poly3");
}
static void PolyDemo(Args _args)
{
abstract class AbstractClass
{
}
abstract public void abstractMethodOne()
{
}
abstract public void abstractMethodTwo()
{
}
public void ConcreteOne()
{
info("ConcreteOne() of AbstractClass");
this.abstractMethodOne();
}
class ConcreteClass extends AbstractClass
{
}
public void abstractMethodOne()
{
info("abstractMethodOne() of ConcreteClass");
}
public void abstractMethodTwo()
{
info("abstractMethodTwo() of ConcreteClass");
}
static void PolyDemo(Args _args)
{
AbstractClass abstractClass = new
ConcreteClass();
abstractClass.ConcreteOne();
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 108
Intf intfReference = new Poly1();
intfReference.implementMethod1();
intfReference = new Poly2();
intfReference.implementMethod1();
intfReference = new Poly3();
intfReference.implementMethod1();
}
Understand the calls and sequence in this
program.
An abstract method is called in class AbstractClass
in a concrete method ConcreteOne(). This
demonstrates the true use of Abstract classes and
abstract methods.
Check the output of above programs and understand the different variations of polymorphism.
Program 2 shows the advantage of Abstract methods.
Eventing in X++ Classes
In AX 2012, a new concept was added to classes, Delegates and Eventing. When a method is run,
we can add event handlers that will run before or after the method execution is done. These events
work like methods that execute before or after a method execution is done. These events can be taken
like a button click in any other programming language where, the event is called immediately when a
button is clicked by the user. In the same way once the method is called, the event is triggered, where
the method attached to the event gets executed before or after the method is called. There are 2 types
of events, Pre and Post. As the name indicates, Pre events are called before the method starts execution
and Post will get executed after method execution is done. Lets see a small example on how to create
events and add the events to methods step wise:
(i) Identify the method for which you like to write event.
(ii) Right click on the method and click New Event Handler Subscription. This will create a
new event handler. Go to properties of the event handler created just now and give a
good meaningful name.
(iii) You can see a property, CalledWhen. This property is used to set when the event should
be triggered. Select Pre/Post based on the requirement. Ive selected Post as in my case,
I like to execute the event after the method execution is done.
(iv) Now, write a method that should be executed after event is called. This method should
be attached to the event handler. Write a method that will handle this. The following is
a sample implementation:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 109
public static void eventHandlerMethod(XppPrePostArgs _args)
{
info("This is eventHandler for event demo");
}
(v) If you observe the above method, there is a new type of argument given in the method,
XppPrePostArgs. This argument is used to accept the return values of the method in my
case. We can change the values that are returned by the method here as per business
logic requirement. Note that the method is declared as static.
(vi) Again, come back to properties of events, set the property Class to the class that has the
method just created, which is used as event handler.
(vii) Now, set the property Method to the method name just created, here, it is,
eventHandlerMethod.
(viii) We have one more property, EventHandlerType, which has X++ and Managed. We will
see Managed code in coming text. For time being select X++ as we are using the X++
method/code as event handler. The following is an example of above discussed steps:
class EventDemo
{
}
public void EventMethod()
{
info("ChildMethod() of PolyChild");
}
public static void eventHandlerMethod(XppPrePostArgs _args)
{
info("This is eventHandler for event demo");
}
static void EventHandlerDemo(Args _args)
{
EventDemo eventDemo = new EventDemo();
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 110
EventDemo.EventMethod();
}
Output:
Note: We will see methods in various other objects like, Tables, Maps Queries etc. The events only work
on class methods and cannot be declared and used on methods of other objects.
X++ Attributes
Microsoft Dynamics AX supports attributes being assigned to X++ code. This allows for a
metadata to be built. It describes methods and types that are defined within X++ code. Attributes are
defined as classes that are derived from the SysAttribute class. The following code for the
DemoAttribute class is an example of an attribute class:
public class DemoAttribute extends SysAttribute
{
str sData;
}
public void new(str _sData)
{
Super();
sData = _sData;
}
public star GetMetaData()
{
Return sData;
}
[DemoAttribute(AttributeValue)]
Class AttributeUsageClass
{
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 111
[DemoAttribute(AttributeValue)]
public void AttributeUsageInMethod()
{
}
With a hope that we have covered good stuff with OOPs, Ill move to the few points of OOPs and
classes in X++.
From the properties of class, we can find
an interesting property, RunOn. This property
tells where the code should be executed, i.e. in
client tier or server tier. There is one more
option, Called from which will execute the code
at the tier which it is called. We will discuss more
about this in coming text.
The advantage of AX is X++, the most
powerful language that provides various
facilities. Being an ERP, the vast functionality
provided by AX should be reusable so that the
developers can reuse the existing code for
extending/updating the functionality as per their
business requirements and the classes play a vital role in AX 2012 and X++.
There are 2 main kinds of classes available in X++. They are:
System Classes
System classes often called as kernel classes are implemented in C++. The source for these
classes is not available, but documentation is available for few of the classes. Some of the
types of System classes are as follows:
Collection classes, which are used to maintain collections like Lists, Sets etc., in AX.
Integration classes like ODBCConnection, which is used to connect to foreign database.
File IO classes like CommaIO, which are used to do IO operations on files.
Application Classes
Application classes are implemented in X++. They are available in the Classes node in the
AOT. These are the general classes that provide application functionality. We will see few
classes in coming sections while we discuss about extending the functionality in AX.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 112
Run on:
The Run on property of a class specifies where the class should run exactly i.e. whether it should
run on which tier, Server or Client or the tier in which it is called. In addition to the run on property,
there is a modifier for every method you can specify where the code should run. The following code
snippet shows this:
server AmountMST findBalancePerDate(TransDate _transactionDate = systemDateGet())
{
return TruckExpenses::findBalancePerDate(_transactionDate);
}
In the above code snippet, you can see the keyword server which says that the method should
be run on server. In addition, you can also specify client which makes the code to run on client. Setting
the property RunOn to Called from means the object will be executed on the tier used by the calling
object. Menu items and classes also have the property RunOn. The default setting for menu items is
running on the client. Classes are by default set to be called from. The default table methods accessing
the database for inserts, updates and deletes cannot be overruled. These methods will always run server
side. Forms should always run on the client. By default an object will be executed on the tier the object
is called from. The primary purpose of changing the tier the code runs is for optimizing the execution
speed. Static methods of the class can have their own AOS modifier.
You will understand the execution on Server and client in coming text with various scenarios in
examples throughout the book.
Exception Handling
An Exception is an unexpected situation where the program terminates with an output which
cannot be predicted. Exceptions are caused due to various situations like:
Trying to access a file which is not there in disk.
Trying to fetch records from ODBC table which is not there in database.
The above are a couple of scenarios where we may encounter an exception. These are just a
couple of scenarios and we may encounter tons of scenarios while we do actual development. If we
dont handle these exceptions, we run into problems with unexpected closing of application while the
application being executed. In order to avoid these situations, we use a technique, Exception Handling.
X++ exception handling techniques makes X++ a convenient and robust programming language. The
exception handling is done in X++ using Try-Catch-Retry statements.
Lets see the statements in application with a sample as follows:
public void div2numbers(int _number1, int _number2)
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 113
real result = 0;
try
{
if(_number2 == 0)
{
throw error(Zero value in denominator.);
}
return = _number1/_number2;
}
catch(Exception::Error)
{
error(An exception was thrown and caught);
}
return result;
}
In the above program, we are using try and catch to handle the exceptions. If you observe the
code in the try statement, there is a code snippet that may cause error. If that code is encountered, it
will throw an exception. Usually, exceptions are throws in certain situations by runtime environment. In
few cases, we may need to throw exceptions manually through code and we use throw in those kinds of
situations. Here, in the above example, a full-fledged scenario is presented. Following are the scenarios
handled:
Try is used to write sensitive code that may cause exception.
The exception situation is manually verified using if condition.
If exception case is encountered, a throw statement is used to invoke exception.
A catch statement is added to handle the exception.
Once the exception handling is done, the return statement will execute as usual.
We can use a retry statement in catch statement to try again. Its better to use a
conditional retry rather than simply placing retry. An example will be as follows:
catch (Exception::Error)
{
if(retryCount < 3)
{
retryCount++;
retry;
}
}
As you can understand from the above statements, if we dont handle exceptions, the runtime
environment will throw exception and will stop execution. If we handle the exceptions, after exception
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 114
is raised, the statements after exception handling is done will continue execution continuously as usual
which will make the program run smooth.
Exception is an Enum which has all the types of exceptions declared. The following are some of
the exception types available in Exception which can be used in catch statement:
Exception Type Description
Deadlock Thrown when there is a database deadlock because several transactions are
waiting for one another.
Error Thrown when a fatal problem has occurred and the transaction has stopped.
Internal Thrown when Microsoft Dynamics AX encounters internal development
problems at runtime.
Break Thrown when the user presses Break or Ctrl+C during runtime.
CLRError Thrown when a problem occurs during the use of the Common Language
Runtime (CLR) functionality.
CodeAccessSecurity Thrown when a problem occurs during the use of
CodeAccessPermission.demand().
UpdateConflict Thrown when a record being updated within a transaction that is using
Optimistic Concurrency Control, and the recVersion value of the record buffer
and the database record are not equal. The transaction can be retried. We can
use a retry statement in the catch block.
Collection Classes in X++
Collections are group of similar type elements. Collections are used to group the similar types in
one entity which can be passed as arguments or can be used for different purposes. A collection is a set
of similarly typed objects that are grouped together. Objects of any type can be grouped into a single
collection of the type Object to take advantage of constructs that are inherent in the language.
Collections work similar to arrays except that they are dynamic unlike arrays which are fixed in
size. Values in collections are usually accessed using the enumerator or iterators associated with
collections. Collections capacity depends on internal architecture and other considerations. Collections
avoid you development effort of data structures or other complex types and can be used directly.
The X++ language syntax provides two compound types which are used to store groups of object
or values, arrays and containers. They are useful for aggregating values of simpler types and cannot
store objects in arrays or containers.
The Microsoft Dynamics AX collection classes have been designed for storing objects. These are
system classes and provide maximum performance. Following are the collection classes (formerly called
as Foundation classes) that can be used in X++:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 115
Collection class Description
Array An array is similar to array in X++ that can hold values of any type
including objects or table records. You can access objects in specific order
or by using the index value.
List List will have the elements that can be accessed sequentially. List class
provides few other methods like addStart() which can be used to add
elements to the collection at starting.
List class provides Enumerator and Iterator for accessing elements.
Iterator can be used to insert() and delete() elements from the list.
Map Map is the collection that can store elements of simple type or objects
that has key value with another value. Every value in the map is
associated with a key which can be used for identifying the object. The
key value pairs can be used in various situations for unique identification
of elements and passing objects.
Set Set store objects in a manner that increases performance. You have in()
and remove() methods and a great advantage of using set is, set will
identify the duplicate and will take only unique values. If you try to add a
duplicate, it will ignore the value you try to add without any error.
Struct
The struct type in X++ is similar to other struct types which can
store values of more than one type at a time. It is recommended to
add the values that are interrelated or group the information about
a specific entity.
Array
The Array class is an array abstract data type that holds values of any single type, stored
sequentially. This acts just like array, but the array will extend when you add new values to it. This
expansion will be a great advantage over standard X++ arrays where the array is expanded whenever
you add new values to it and no specific dimension is required.. The major advantage is, objects of the
Array class can be transferred to functions and methods as arguments and this class can hold objects
and records, unlike the array data type that is built into the X++ language.
The following simple example demonstrates the usage of Array:
static void CollectionArray(Args _args)
{
Array oarray = new Array (Types::Integer);
int i;
oarray.value(1, 100);
oarray.value(2, 200);
oarray.value(4, 300);
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 116
info(oarray.toString());
info(oarray.definitionString());
for(i=1; i<= oarray.lastIndex(); i++)
{
info(strFmt("Value at index %1 is %2", i, oarray.value(i)));
}
}
As you can see in the above example, Array is created using the class Array and you pass the
type of the values being stored in the array. You can add the values to array using the value method
where you pass the index and value into the object. The toString() will print the values that are there in
array and definitionString() will print the definition. You can always get the value in particular index
using the value() method of array class passing the index value. Please note that the types have various
types and the following example will show you how to store objects in array:
static void CollectionArrayWithObjects(Args _args)
{
Array oarray = new Array (Types::Class);
ArrayObjectDemo ob;
int i;
oarray.value(1, new ArrayObjectDemo(10));
oarray.value(2, new ArrayObjectDemo(20));
oarray.value(3, new ArrayObjectDemo(30));
info(oarray.toString());
info(oarray.definitionString());
for(i=1; i<= oarray.lastIndex(); i++)
{
ob = oarray.value(i);
info(strFmt("Value at index %1 is %2", i, ob.getValue()));
}
}
The code for ArrayObjectDemo class used in above example is as follows:
class ArrayObjectDemo
{
int value;
}
public void new(int _value)
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 117
{
value = _value;
}
public int getValue()
{
return value;
}
Check how the objects are added, the type is declared as class in constructor while
creating the Array object. While accessing the value, it is taken into the class reference and is used in
info as you can see. You can pass this object to any method of class or some table etc. Now, check the
following example of list:
List
static void CollectionList(Args _args)
{
// Create a list of integers
List list = new List(Types::String);
ListEnumerator le = list.getEnumerator(); //Get enumerator
// Adding elements to the list
list.addEnd("One"); //Add element at end.
list.addEnd("Two"); //Add element at end.
list.addStart("Three"); //Add element at starting.
// Print a description of the list
info(list.definitionString());
info(list.toString());
//Use enumerator to retrieve values.
while(le.moveNext())
{
info(strFmt("Item is %1", le.current()));
}
}
Just check the output and you will understand how addEnd() and addStart() works. Lists are
structures that can contain values of any X++ type and should be of same type. You cannot add values of
different types into list. List class contains any number of elements that are accessed sequentially.
Notice how the list is created using List class and its constructor. If you observe the constructor,
the Types argument is passed and you have to add the values of similar type of values to list. The type of
the values in the list is specified when the list is created and cannot be changed afterward.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 118
You can add values to lists at start or at end. Lists can be traversed using ListEnumerator or
ListIterator as shown in above example. You can traverse using the moveNext() of ListEnumerator and
the current() method returns current object or value enumerator holds. In addition to traversing, you
can use ListIterator for performing insert() and delete() also. If you like to get the object stored in
enumerator, you have to store that into reference of similar type.
Map
Consider a case where you like to identify some value or object using a key or you like to send
the values to a method or some function in Key - Value pairs, in that case, you should consider using
maps. Map class allows you to associate one value with another value and the values are called as Key
and Value and both should be X++ types. Map can also store objects like Arrays or Lists. The maps are
used with key value pairs to make access fast and easy for the developer. The following example shows
you how to use Maps.:
static void CollectionMap(Args _args)
{
Map map = new Map(Types::Integer, Types::String);
MapEnumerator me = map.getEnumerator();
map.insert(1001, "Zone1");
map.insert(1002, "Zone2");
map.insert(1003, "Zone3");
while(me.moveNext())
{
info(strFmt("Key : %1, Value : %2", me.currentKey(), me.currentValue()));
}
map.remove(1001);
me = map.getEnumerator();
while(me.moveNext())
{
info(strFmt("Key : %1, Value : %2", me.currentKey(), me.currentValue()));
}
info(map.lookup(1003));
if(map.exists(1005))
{
info(strFmt("Value for 1002 is %1", map.lookup(1005)));
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 119
}
else
{
info("No value associated for key 1005.");
}
}
Notice how Map is created using the constructor passing the Types for key as well as value. You
can also pass objects of any class type to map. insert() can be used to insert an element into map and
the MapEnumerator can be used for traversing through the map and use the currentValue() method to
get the current value in enumerator or use lookup() for finding the value based on key and check for
existence of a key using exist(). Notice the moveNext() in enumerator which is used to check existence
and move to the next value in the enumerator. This works similar to moveNext() of list and can be used
to traverse through the map.
Set
Set will store values or objects by ordering them for quick accessing and traversing. Set class
stores only unique values and if you try to add a duplicate value, it will simply skip the value and will not
duplicate. These can be used as Keys as they will not store duplicates. The following example shows you
how to use Set:
static void CollectionSet(Args _args)
{
Set set = new Set(Types::String);
SetEnumerator se = set.getEnumerator();
set.add("John");
set.add("Adam");
set.add("July");
set.add("Robert");
while(se.moveNext())
{
info(strFmt("Item is %1", se.current()));
}
set.remove("Robert");
se = set.getEnumerator();
while(se.moveNext())
{
info(strFmt("Item is %1", se.current()));
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 120
}
}
Note that Set can also store objects. Notice how the constructor is called using the Types
argument. You can add items to the set using add() method and in the current example, set inserts the
string elements in sorted order. You can check the output for observing how set works. In addition to
insertion, you can also delete the elements from set using remove() method. SetEnumerator can be
used to traverse through the values in a set. There is an interesting method in set, in()which determines
whether a specified element is a member of the set.
Struct
Check the following example of struct:
static void CollectionStruct(Args _args)
{
Struct s = new struct ("int EmployeeId; str EmployeeName");
Struct copyStruct;
container con;
int i;
// Adding values to the items
s.value("EmployeeId", 25);
s.value("EmployeeName", "John");
// Adding a new item; data type is automatically taken from value
s.add("EmployeeSalary", 4500.50);
// Print the definition of the struct
info(s.definitionString());
// Prints the type, name and value of all items in the struct
for (i = 1; i <= s.fields();i++)
{
info(strFmt("%1 %2 %3", s.fieldType(i), s.fieldName(i), s.valueIndex(i)));
}
// Pack the struct into a container and restore it into copy struct
con = s.pack();
copyStruct = Struct::create(con);
info(copyStruct.definitionString());
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 121
Just check how the struct is defined with the list of fields it can hold. You add values to the struct
using the value(). If you try to add a value for without defining field, you will get a runtime error.
Instead, you should use add() of struct to add new values and types are taken by X++ internally. There is
no enumerator for struct, instead, the fieldType(), fieldName() and valueIndex() can be used to get the
different attributed and values of the struct. You can pack the struct to a container and copy that to
another struct or you can create a struct using create() static method of struct class passing a container
to that. The primary use of struct is to bind multiple fields information into one entity which has
relation between them as in the above example, where employee data is stored.
With enough information on collections in X++, its time to move to more interesting stuff
coming in next chapter.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 122
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 123
Data Dictionary
Data Dictionary in AX is a central repository where we can see the list of objects like Tables,
Views, and Enums etc. The data model of AX is seen in Data Dictionary. Data Dictionary is found in AOT.
When modifying or adding new features, this is the place where we add/update the data model. The
data in the data model is stored in Relational Database. Basic knowledge about creating a database
relationship model and how to normalize data will be beneficial when designing your data dictionary
modifications for Axapta. Details about relational database modeling and schemas are out of scope of
this book. We look into what is data dictionary and how to use and organize various elements in Data
Dictionary in AOT in coming text.
Microsoft Dynamics AX uses SQL Server or Oracle for maintaining the database and storing data.
When we create or update Tables, Views, Fields and other Data Dictionary related stuff which is related
to Database, the only part that is stored in AX is definition. All the data is physically stored in actual
database i.e. either in SQL Server or Oracle database. Information related to table relations and delete
actions [these are restrictions imposed on a table to judge whether the record can be deleted or not,
will be discussed in tables section of Data Dictionary] are found in AOT under concerned nodes.
Lets understand the elements in Data Dictionary node of AOT one by one as follows.
Throughout this book, we will follow a requirement project called Road Transport Service Provider
[RTSP]. RTSP is a virtual company that will deliver goods to its customers and resellers using trucks. They
divided their business jurisdiction into zones and each zone is a site where each zone is allocated few
trucks for delivery. We will discuss the requirement further step by step and create the objects we need
at each step.
Extended Data Types
Let us consider a scenario, where we have a field like CustomerId in your requirement. You have
created this field in nearly 100+ tables and used the same field for reference in 100+ code snippets and
declaration sections as the business mostly depend on Customers. The size of the CustomerId was
planned to be 5 in number of characters, but very soon, it grew to 6 and its time to update the field size.
I like to know the plan of action followed by you to get the work done quickly.
Open each table and update the properties of the field CustomerId.
Update at one place which will update at all places.
Im sure, Ill prefer second one than first one if possible as first will consume large volume of
time which will increase cost and decrease productivity and may be error prone as even if the update is
missed at a single place, will lead to data problems which may not be recovered most of the times.
But, how and using what kind of object this is possible? Answer is, Extended Data Type, shortly,
EDT. EDTs are the types created from basic or another EDT. EDTs extend primitive types or other EDTs
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 124
i.e. they inherit properties from other EDTs or primitive types which can be changed according to
business requirements. Following are benefits of creating EDTs:
Reuse of the properties.
Efficient maintenance i.e. the properties of many fields can change at one time by changing
the properties on the EDT.
A table field that is created based on an EDT will also inherit properties from that EDT, which
will make many fields change at one time possible.
EDTs are central part of AX. Whether we create tables or declare fields, we mostly depend on
EDTs as when we update EDTs, it will reflect where ever we use that EDT. We will try to understand how
to create EDTs in the following lab. The requirement for creating an EDT is, for truck table. Trucks are
used to deliver goods from company store/warehouse to customer/reseller store. We need TruckId,
Description and Date fields for trucks master which will need EDTs. Please note that standard AX 2012
will give us around 9000+ EDTs and it seems like we dont need any extra EDTs to define. Lets find if
there are any existing EDTs that support for our requirement:
Description >Description255 will work for the current scenario.
Date > TransDate will work for the scenario to store date.
With enough information, its time to create an EDT for TruckId which is not available, and as
per the requirement we dont have any EDT that suite. So, we will create a new EDT with the following
properties. To create an EDT, follow
these steps:
Expand the DataDictionary
node in AOT;we will see a node
called Extended Data Types.
TruckId is an alphanumeric
and so, we will take string to store
the values. Right click on Extended
Data Types > Click New > String. You
will see a lot of other primitive types
and can be used to create EDTs as
per business requirement.
Now, lets update few
properties. Right click on newly
created EDT and click on properties.
You will find few properties in
adjacent image.
The name property, we will
rename from Type1 to TruckId. Note
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 125
that, we will find few colors in properties of few properties. The reddish color indicates that property
value should be mandatory and yellowish is optional but recommended.
We will add the values to
Label and Help Text as Truck id
and Used to store value of truck
identification number. Note that,
the labels and help text should be
added label ids instead of plain
text but for time being we will add
text and will update once we go
through label wizard which will be
covered in coming text.
We are able to see one
more property called Extends,
which is used if we like to inherit
the properties of another EDT. This
is a kind of inheritance where the
properties of one EDT are inherited
into another. Currently, lets leave
the field as it is. We will get to this
property if required in coming text.
Note that, we can inherit EDTs to
any number of levels as per
requirement and availability as in
below EDT for example:
Now, lets update the
string size to 20, alignment to Left
and Change Case to UPPER CASE
which will satisfy the business
requirement. Once everything is
done, the properties should look as in the adjacent image.
The same way, create one more EDT for the Zone table. The EDT has following criteria:
ZoneId: This EDT is a string which will divide locations into various zones and is a string value.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 126
Once EDT is created and saved, AX may synchronize tables which may take few minutes
depending on the machine performance and it is recommended to continue and not to disturb or close
the synchronizing.
Note: We can update the properties of the EDT at any point of using the application. This will update the
properties of the EDT where ever it is used for instance, the table in database i.e. SQL Server or Oracle,
wherever it is installed. To do this updation, this will synchronize (which will take few minutes) and is
recommended to continue synchronizing though it is a bit time consuming.
If you further expand the newly created EDT node, youll see 2 nodes, Array Elements and Table
References. Array Elements are a powerful feature of EDTs which provide a great advantage. When we
use this EDT to create field in tables or any code snippet, this will create a multi valued variable in code
and multiple fields which is equal to the array size in physical table in database. Let us consider a
scenario, where we need to store the dimensions of truck i.e. length, breadth, volume and capacity of
truck. We will create an EDT as follows:
Create an EDT of type integer and name it TruckDimensions.
Expand the EDT newly created and right click on TruckDimensions, click on New Array
Element. Name it TruckBreadth, give a proper label. You can identify the Index being 2.
Add 2 more array elements namely, TruckVolume and TruckCapacity, you will see that
the indexes are given values 3 and 4 for the newly created array elements.
Finally, check the array length of TruckDimensions, which will be 4.
Now, when you use this EDT, each array element of an extended data type will be created as a
database field. Both from the AOT and from X++ a field based on an extended data type array will look
like, and be addressed as, any other field. Note that the first entry of the array will be entry created
when creating an EDT. All the other entries are created under Array elements node. All the properties of
these Array Elements are inherited from the first entry created i.e. EDT created. We have Label and Help
Text properties which can be set on EDT Array Elements and we can find a node named Table
References if we further expand each Array Element that can be used to configure each Array Element.
EDTs in AX 2009:
EDTs in Microsoft Dynamics AX 2009 are similar to 2012 but, have a node called relations, which
are used to create EDT relations. EDT relations are similar to Table relations, which are used to establish
relations that work throughout AOT where ever the EDT is used. EDT relations are 2 in number, Normal
and Related Field Fixed. As these are obsolete in AX 2012 and only table relations are used, we skip this
topic at this point as we will see the table relations and will discuss the relations in coming text in table
relations. This relations node is replaced by Table References in AX 2012.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 127
Base Enums
Base enumerations, also called Base Enums, are pre-defined text values for a field that are
referenced through an integer value in the database. These are best described/used for categorizing the
data. Let us suppose, we have trucks which should be further categorized. We can have a truck type
which will have a limited set of values like, Small, Medium and Large. These 3 types can further
categorize the records or data in table. The best example using Enums is, Gender, which is used to
categorize a person or employee in standard AX. An inbuilt enum called Gender is available which is
used for this purpose.
We will see how to create a Base Enum practically as follows:
To create a Base Enum, expand, AOT > Expand Data Dictionary > you will find a node
called Base Enums.
Right click on Base Enums and click New Base Enum.
Change the properties as follows:
Name: TruckType
Label: Truck type
Help: Used to categorize the truck based on types.
Note that, the labels and help text should be created using labels. We will update
these labels once we create labels.
You will find few other properties like, Style with values, Combo box and Radio button,
from which we can select one based on the look and feel user likes to have.
Now, add the elements to the enum created. Just right click on the enum created and
click on New Element. Name it as Small and give the label Small. You can see one more
property called Enum Value, an integer value. This is the value that represents the enum
element. This is the actual value that is stored in database table when we create a
record.
Repeat last step for Medium and Large elements.
Once all the enums are created, we should be able to see the following in the Base Enums:
Note: A Base Enum can have maximum of 255 entries. This property will be by default given by
AX starting from 0, incremented one by one. We can change the Enum Value to some other value based
on requirement. When we use these enums in relations, we should use integer value instead of text. We
can access the Enums in code using :: operator in the conditional statements etc. Base Enums can be
used in tables as well as X++ code also.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 128
Tables
Tables are basic persistent store used to store data in the form of records. Tables in Ax are
created physically in database i.e. SQL Server or Oracle. Tables store data in the System. They contain
various fields of different data types depending on the type of data they hold. Data in the tables is
usually entered, deleted, edited and updated through forms by the end users. Developers will have
accessing of table browser, which can be used to perform CRUD [Create, Read, Update and Delete]
operations on tables.
When Microsoft Dynamics AX is installed in machine, AX creates the database with complete
table repository. This will have the following types of tables:
System Tables
These tables store information about metadata. Most of the cases, these are non-
changeable.
These tables are mostly used by
application and are used by kernel to
handle tasks like keeping application in
sync with database, handling licenses and
configurations etc.
We can find these tables in AOT >System
Documentation>Tables. Few tables are
prefixed with Sys are also system tables
which can be found under AOT > Data
Dictionary > Tables as in the adjacent
figure.
It is not recommended to modify structure or update data in System tables to avoid
unwanted results.
Most of the cases, the data stored in System tables is not company specific and is
related to all companies like, licensing information.
Application Tables
These tables are general tables are used to build and store application data related to
modules in AX, for e.g. InventTable stores details of Items and SalesTable stores the data
related to Sales Orders.
These tables usually store data specific to company as AX can support multiple
companies functionality.
We can create these kinds of tables at any time and update these based on business
requirements. All the tables other than System tables are application tables.
These are also created by AX while installing AX database and can also be created once
after the installation is done. When we create or modify these tables, they are physically
created or updated in SQL database.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 129
With enough information, lets move to create a sample table which will update our knowledge
to get into more challenges. The business requirement is, creating a table to store details of trucks. We
will create a table called TruckTable, a master table which will have the fields, TruckId, Description,
DateOfPurchase, TruckDimensions and TruckType. Note that, we may add/modify the table structure
based on updating requirements day by day at any point.
Note: While working with any of the exercises in this book, create a project following the
structure of AOT, as a better practice to organize your projects well. To create project and create
structure, click on projects shortcut, which will open projects window. Create a new project and open
the project. Now create groups and sub groups looking the AOT structure and follow the exercises.
When you create a group, update the Name and Project Group Type of the newly created group.
Follow the steps to create a new table as follows:
Open the project created for practice exercises. Expand AOT > Data Dictionary.
Right click on Tables group and click on New > Table.
Open the properties of newly created and update the properties as follows:
Name: TruckTable and Label to Truck table
Now, expand the TruckTable, which is created just now. You will see various nodes
namely, Fields, Field Groups, Indexes, Full Text Indexes, Relations, Delete Actions and
Methods. We will discuss about these in coming text extensively.
Its time to add fields. To add fields to the table, right click on the Fields node and click
on New > Select the type for e.g. String. Now, select the properties of the field just
created and change the name to TruckId, Label to Truck id, Help Text to Truck
identification number and EDT to TruckId.
There is an important point to remember while selecting the EDT in properties of field,
we have to select the EDT which is created using the primitive type the field is created
with to avoid the errors. For example, here, we have created the TruckId field as String
and TruckId EDT is created using String primitive type. If we try to assign an EDT created
with a type other than string to the field, we end up with unwanted results. After
updating the fields, we see the properties window as in the following image
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 130
Note a bar [this will be red in color in your AOT] left side of the table TruckTable. This
indicates that the table is not saved. We can also find few other properties. We will
understand about them once we are done creating the table.
Now we will create one more field, Description. Instead of creating a new field and
setting the EDT property, just drag and drop the EDT into fields of table TruckTable. Click
on AOT, Expand Data Dictionary node, expand Extended Data Types node, find EDT
named Description255, drag and drop in to the fields node of TruckTable. Update
properties Name, Label and Help Text of the field. You can see that the EDT property is
set already.
In the same way, create other fields namely, DateOfPurchase, TruckDimensions and
TruckType. Please search for TransDate EDT for DateOfPurchase and use the EDT
TruckDimensions and Base Enum from TruckType. Note that, we can use Base Enums to
create fields of tables.
Now lets update few properties of fields as per the business requirement. The TruckId
is a mandatory field that is assigned for each truck. We have the properties Mandatory,
which will specify whether the field value is mandatory or not. Set this property of field
TruckId to Yes.
Once the TruckId is created, it should not be allowed to modify as the Id value will be
generated while creating the record for the first time. We have couple of properties
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 131
namely, AllowEditOnCreate and AllowEdit, which will provide this functionality.
AllowEditOnCreate, if set to Yes, will allow us to edit the value of the field while record
is being created and AllowEdit will allow us to edit the fields value while we update the
record further. As we need only the editing to be possible only when we create record
for the first time, set AllowEditOnCreate to Yes and AllowEdit to No.
The other important property is, Visible which will make a field either visible or invisible
based on the value of the property.
In the same way described above, set the properties of other fields as per business
requirements.
As we have created a table, now its time to enter some records into the table. Right click on
table and click on Open or click on Add-Ins > Table browser to open the table browser. We will see the
following columns:
TruckId
Description
DateOfPurchase
TruckDimensions
TruckDimensions
TruckDimensions
TruckDimensions
TruckType
dataAreaId
recVersion
RecId
We will understand few points after looking at the list of fields available:
There are 4 columns created for field TruckDimensions as this is an EDT array which has
ArrayLength 4.
There are 3 other fields in the list which are not created by us. These are created by AX
for all the tables we create to maintain few details as follows:
dataAreaId is used to store the company id which is used to store and retrieve data
specific to company we are working with currently. For example if a firm has 2
companies named 01 and 02, this field will store the details of company id when a
record is created as in the following figure:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 132
Note that the records of one company is not displayed in another company except
in some cases where we manually do customizations or write code to get multiple
companies records or retrieve multi company records as per business requirement.
recVersion is used to update the information of record version, like if we edit and
update the record, the version is changed accordingly and this field value is
updated.
RecId is the unique identifier used by AX to identify the record. This will store a
unique id which can be used by AX as well as developer to identify the record in
table uniquely.
Once the table is created, the structure is created in AX and the actual physical table will
be created in Database also. We can see the table created in database and AX in the
below images:
Note that, there are no EDTs in the fields of table in SQL Database, instead, they
maintain their own types. When we update the EDTs in AX, the fields properties are
updated accordingly in physical SQL Database.
We can see that there are 4 fields created for TruckDimensions as it is a field created
using EDT Array of ArrayLength 4.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 133
You can try writing a query to fetch records on SQL Database table and check the
existence of table and records, though it is not recommended.
Above discussed 3 fields are created by AX environment and cannot be modified by user
in AX environment and is not recommended to update them to avoid unwanted results.
Now, enter couple of records into table from Table Browser. Just click on New to create a new
record, enter the values accordingly. Note that the mandatory fields are underlined in red and internal
validations like mandatory and other field validations are done by AX with the inbuilt code. Once records
are entered, we can modify the records and save the records and even can delete the records. The
shortcuts for CRUD operations are: Ctrl + N for new record, Ctrl + S to save a record and Ctrl + F9 to
delete a record.
Use of EDTs Revisited
In the above images taken from physical database, we can see that the TruckId is created of type
nvarchar(20) but not EDT TruckId. Let us assume that the EDT is used in multiple [assume 20+] tables.
Now, if we update the EDT in AX, it will update the physical database also. Its the great advantage in AX,
which we dont find in most of the other languages/packages.
We have few EDTs like Bitmap which can be used to store images and we can also use
Containers to store large objects. For example we can see a table called CompanyImage, which will store
logos or company related images. It is recommended not to load the table with very big images. A fair
image which can solve the business requirement will work as large amount of data like images may
cause performance drawbacks.
Company
Remember the dataAreaId field, which is created by AX while we create a new table. This field
stores the company id, which is used to identify for which company the record belongs to. This makes
possible to display only the records that are related to particular company. The application look, feel and
functionality will not change except that the records of that particular company will be visible in AX
environment, where we use the table.
At the same time, if we open physical database and query for the table records, we will see all
the records and dont find company specific. There is only one variation we can see, the field dataAreaId
will be populated with the company ids which are configured in AX. The multiple companies concept
which uses dataAreaId functionality has numerous uses in which some of them are:
You wish to train your staff which may need some test data that can be created as a
new company and can be used for testing and training needs.
You may have multiple legal entities which may have different data and may need to be
filtered accordingly. In that case, we may need this dataAreaId and the multi company
functionality.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 134
There is a system table called DataArea which will store the list of companies available in AX
environment configured and some other information related to a company. You can open the table
using table browser from Tables node of System Documentation node in AOT. It is recommended not to
modify or edit the records in this table which may lead to unwanted results.
Few properties of fields
ID : This is a unique value generated by AX for any field/node in AOT.
Type: Indicate the type of field.
Configuration Key: Set the configuration key for the field. We will see more about configuration
keys in coming text.
GroupPrompt: Specifies a label for the field when it appears in a group.
IgnoreEDTRelation: Used in EDT (Extended Data Type) relation migration. When migrating
relations from an EDT node to a table node, you may skip an invalid relation for a given table field.
Model: Specifies which model the field belongs to .
SaveContents: Determines whether the field data is saved in the database or is treated as virtual
field data. Virtual field data is calculated at run time when the field is displayed, and has no physical
representation in the database.
Type: Specifies the base type of a field.
Visible: Determines whether the field should be visible in the user interface.
AliasFor: Determines the table field for which the field is an alias.
Adjustment: Determines whether the string field should be left or right aligned when it is stored
in the database.
Field Groups
Field groups are used to create groups of the
fields which are used in Forms/Reports. Field groups
in Microsoft Dynamics AX are logical groupings of
physical database fields. Microsoft Dynamics AX uses
Field Groups to cluster fields into logical groups so
that these groups can be used on forms and reports.
Once we create a group and use this group in
forms/reports, the forms/reports will update
automatically if we change/update the field group.
The technology which updates the layout of
forms/reports accordingly when we change the field
groups is called as IntelliMorph. IntelliMorph adjusts
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 135
the layout of forms and reports which use the modified field group. Adding new fields to a FieldGroup
can be a great advantage and advanced technique for updating forms that use field groups with new
field or removed fields.
There are few field groups already defined by AX which can be used by us for grouping of fields.
If the existing field groups are not sufficient, define a group when several fields logically belong together
like, for example, all the fields related to address can be taken as AddressGroup and are shown together
on forms. A best practice is, every field on a table should belong to a field group.
We will create a field group, General, which we can use to group our table fields. To create a
field group, expand the newly created table, right click on Field Groups and click on New Group. A new
group will be created with the name Group1. Update the name to GeneralGroup and label to General.
Once group is created, add the fields to the field group. To do this, drag and drop all the fields from
fields to the field group General. You can reorganize the fields, i.e. the order they should be displayed on
the form/report when we use this field group on form/report. As we dont have much number of fields,
we are creating only one field group. You can add any number of field groups based on your business
requirement. We will see more about field groups in forms section.
There are other field groups available in Field Groups section on table namely, AutoReport,
AutoLookup, AutoIdentification, AutoSummary and AutoBrowse. We will see about the AutoLookup in
coming text. AutoReport is used for getting report without developing the report physically. When we
add the fields to AutoReport and try to print [press Ctrl + P] from Table browser, it will generate the
report from the fields in AutoReport. Other than these 2 field groups, 3 are added in AX 2012, which are
not there in AX 2009 and are used for Summary and Identification purposes.
Indexes
Indexes in Microsoft Dynamics AX are used to speed up the search of tables data. Indexes are
used to locate records. These indexes in the table definition are the physical indexes that exist on the
tables in the database. These indexes are stored separately in the database which will contain a key that
can be located very quickly located in the index and a reference to the record.
An example is, ItemIdx of the InventTable which contains the Id of the Item. This ItemId is
unique and can be used to lookup Item record quickly from the table through the reference.
Indexes are classified into 2 types: Unique Indexes and Non-unique Indexes. Unique indexes are
used to avoid duplicate values for a column. When we create a unique index, AX ensure that the value in
the column will not get duplicated and if an attempt is made to duplicate the column value, AX throws
error, and will not save the record until the value is update to a new non-existing value in the table,
which will allow us only to insert the unique values.
To increase the performance, we can create few more indexes which may be non-unique. These
are usually created to fetch the records faster based on a field, which we use to filter the records.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 136
Instead of performing full table search of all records in table, these provide a quicker way to retrieve
records from the table.
We can create multiple unique and non-unique indexes, but it is recommended to create
optimal number of indexes to improve the performance. Indexes which are not required can cause
performance drop in updating process of records because, they use space of system and should be
updated every time a record is created/edited/deleted.
Any table in AX must have at least one index. Though it is not mandatory and you dont create
one, it is recommended and at least RecId can be used to create an index if there is no requirement to
create index. This kind of situation may arise in case of Transaction tables where there will be duplicate
records and we may not find a key column. So, while creating indexes, be aware in order to create
indexes to increase performance and to avoid performance drawback as each time data is changed in
table, database will update the indexes for that table.
Indexes in AX table are created under Indexes node of table. Indexes can be created on one field
or multiple fields. Indexes are only created on the table fields. With enough information, its time to
create an index for our table, TruckTable. A small note before creating index, a field of data type memo
or container cannot be used in an index. Follow the below steps to create an index:
Expand TruckTable [or expand the table you need index for].
Right click on Indexes node and click on New Index.
An Index with a name Index1 will be created. We will name it TruckIdx. Please note that,
though it is not mandatory, it is recommended to suffix with Idx to identify the indexing
fields and better to reflect the name of the field for better identification.
You can find few other properties called AllowDuplicates, Enabled and
ConfigurationKey. AllowDuplicates set to No will make an index unique index. Enabled
will enable/disable index and ConfigurationKey is used to set the configuration key to
the Index.
Drag and drop the field TruckId on to the TruckIdx or right click on the TruckIdx and click
on New Field, select the DataField in the properties of index field dragged.
Now, your index is created. But as the field is a unique field and should not be
duplicated, go to properties of TruckIdx and update AllowDuplicates to No. This will
create a non-unique index.
You can create multiple field indexes also. Just add multiple fields instead of one field to
index.
Primary Index
A primary key is used to identify a record in the table uniquely. A single column unique index can
be used as primary index in AX. Primary Index in AX is a unique index for a table which define primary
key for the table. Creating unique indexes is shown in the above section. An example for primary index
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 137
is, ItemIdx of InventTable, where ItemId is used as primary key and the PrimaryIndex property of
InventTable is set to ItemIdx.
To set the primary index for a table, open the properties window of the table and set the
PrimaryIndex property to a unique index. We can set any unique index to PrimaryIndex property, being
field should be mandatory and better cannot be edited. Remember that you need to set the property
AlternateKey of the index to Yes before proceeding to below steps. Lets set the TruckIdx as
PrimaryIndex to TruckTable:
Open properties of TruckTable.
Set the property PrimaryIndex to TruckIdx and save the table.
Note: Indexing is a preprocessing task
i.e. indexes for a table should be
created before data is inserted into
table. It is recommended to create
indexes before inserting records into
table to avoid errors and synchronizing
issues.
When there is no indexing field
available [may not be required] in a
table, we can use the RecId for
indexing. This is called as SurrogateKey, which is used to index using RecId. SurrogateKeys are added in
AX 2012. AX 2009 has a property CreateRecIdIndex, which can be used to create RecId as indexes. This
key is used on many relations between tables and may be DataAreaId few times, if record is saved per
company.
Cluster Index
Cluster Indexes are also allowed in AX 2012 tables. A clustered index determines the physical
order of data in a table. Because the clustered index dictates the physical storage order of the data in
the table, its better to have a table that contains only one clustered index. However, the index can
comprise multiple columns. A clustered index is particularly efficient on columns that are often searched
for ranges of values. Clustered indexes are also efficient for finding a specific row when the indexed
value is unique. It is a better practice to make index as Cluster Index if only one index is created on the
table. Any non-unique or unique indexes can be used as Clustered Index for AX table. To set an index as
cluster index to a table, in the properties of table, select ClusterIndex property with appropriate index
you need. After you insert data in table, open table in either AX or physical database, you will see the
records ordered in the order of field which is clustered indexed. The following are the advantages and
disadvantages with cluster indexes:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 138
Advantages:
Search results will be much quicker when records are retrieved by cluster index,
especially if records are retrieved sequentially along the index.
Other indexes that use fields that are a part of the cluster index mostly use less data
space.
Disadvantages:
Takes longer to update records provided, when the fields in the clustering index are
changed.
It is recommended to create and use cluster indexes very few in number due to its
disadvantages and also, avoid clustering index constructions where there is a risk that many concurrent
inserts will happen on almost the same clustering index value which will lead to performance issues.
Full Text Indexes
Full text indexes are added in AX 2012. A full text index contains information of location about
each significant word in a string field of a table. Queries can use this information to run more efficiently
and complete much sooner. These queries search for words that are in the middle of the string field in a
table. In X++ SQL this is done with the keyword like and the * wildcard character, such as in the code
phrase like "*diamond*".
Now, we will add one more table, Zones with the fields ZoneId and Description that identifies a
Zone as each truck should be associated with a Zone. This table is an assignment and we will follow with
the next topic.
With a hope that we have covered all the topics in table indexes, its time to move to next topic,
Relations.
Relations
As every truck should be associated to one of the zones, we will add Zone to our table,
TruckTable. Field ZoneId is added using an EDT ZoneId created for the table Zones. Just drag and drop
the ZoneId into the fields of TruckTable. If you open the table with table browser and add a record, you
can add the value to ZoneId which is not present in Zones. This looks weird but a fact that there is no
relation exists between two tables. Here, we need a relation of type Foreign Key to enforce referential
integrity constraints.
Relations between two or more tables are used to associate rows of one table with another
table. We can also enforce referential integrity using the relations. AX 2009 supports relations at table
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 139
level as well as EDT level. As EDT relations are obsolete, we will restrict our discussion to table level
relations with a small conclusion on how EDT relations work. Relations are used for following purposes:
Enforce business rules in tables by providing integrity constraints.
Create joins in forms and queries automatically with other related tables.
To display the lookups in other detail tables from master table.
Relations can be used to update changes from one table to another.
Relations are created in the table. We will find a node called Relations for each table in AOT,
which is used to create relations for that particular table with another table. There are few notable
points with table relations in AX:
AX relations are at application level and are not at physical database level i.e. the
relations created in AX are not created in SQL Database. AX will maintain the relations.
So, if you perform any operation at physical database level, these relations will not
reflect.
Relations manage the relationships between data in different tables like foreign key
relationship and mostly formed from parent table.
We can create the following types of relations in AX under Relations node of table:
Normal, which specifies the related fields in another table. This is a basic relation that
should be created. We can also include multiple fields in this relation. For example, if
you open CustTable and expand Relations node, you can find the relation named
CustGroup, which is established with table CustGroup. The values in CustGroup field of
CustTable are limited depending on the availability of records in CustGroup. We cannot
add a value to this column in CustTable which is not available in table CustGroup.
Field Fixed, which specifies related fields that restrict the records in primary table. The
column used to specify this kind of relation is usually an Enum.
Related Field Fixed, which specifies related fields that restrict the records in related
table. Like Field Fixed, this is also an Enum usually.
Foreign Key, which specifies an association between a fields in the current table to the
primary key field in another table. The current table field is called as foreign key field.
With enough information, lets create a foreign key relation as follows:
After creation of ZoneId EDT and Zones table, open the properties of EDT ZoneId and
update the property ReferenceTable with ZonesTable as this is the master table which
will have ZoneIds. Note that, ZonesTable should have a unique index with field ZoneId
and this should be set as PrimaryIndex for the table ZonesTable. Finally, create a new
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 140
table references under Table References node of the EDT ZoneId with the properties,
Table = ZonesTable and RelatedField = ZoneId. The ReferenceTable property identifies
the table that is referenced by this EDT, and which has the primary key as well as
indicate the primary key table
which this EDT references and each
EDT node in the AOT contains a
new Table References node that
stores lookup information.
Drag and drop the ZoneId EDT to
the fields of TruckTable. You will
get a popup as shown in adjacent
image. Click on Yes to create an
automatic ForeignKey relation,
which will create a relation of type
foreign key automatically. This is
done by AX environment for us that
will reduce our effort.
Once the relation is created, you
can see the relation under the
relations node of table as seen in the below image.
Note that, we can add multiple relations from multiple tables or from same table as per
business requirement.
Add few records to ZonesTable and open the TruckTable. You should see a dropdown for
ZoneId field as in the below image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 141
The dropdown which you see is called as lookup. We will discuss more about lookups in
coming text. This lookup contains the list of ZoneIds from which we can select one value. Try
to type a value that is not there in the drop down, you will get an error. In this way, we can
enforce integrity constraint.
Note: In the above image, we are able to see only one column in lookup of ZoneId, ZoneId. To display
multiple fields, we can do one of the following:
Set the properties TitleField1 and TitleField2 of table [in this case TruckTable] to the
desired fields you like to display in the lookup. This will display 2 columns in each row so
that the user will get more information about the Zone in lookup.
If you need still more fields in the lookup, then add the list of fields to the field group
AutoLookup. This field group will fetch all the columns into the lookup for each record
which will give much more information to the user to select the exact record needed.
We will see more about lookups in Forms section.
Now, we will create a new relation manually. Lets delete the foreign key relation just created
and create the relation manually as follows:
Expand the TruckTable and right click on Relations node.
Click on New Relation, open properties of Relation just created.
Update the properties Name and Table with the values ZonesTable. Though it is not
mandatory, it is recommended to name the relation reflecting table name for easy
identification.
Right click on newly created relation, click on New > Normal.
Click on properties of newly created node in last step and update the values Field with
ZoneId and RelatedField with ZoneId. This will create our relation between 2 tables.
Open the TruckTable, check the field ZoneId for the lookup to make sure that the
relation is established fine.
With enough information, we will move to other types of relations, the Field Fixed and Related
Field Fixed. These are special type of relations that are used to filter records. Before creating the
relation, create a Base Enum, ZoneType with values Big, Medium and Small and add the same as field to
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 142
ZonesTable and add few records with the different values and different ZoneTypes. The records existing
in my table are as follows:
With simple Normal/Foreign Key relation, when you check the lookup in TruckTable for ZoneIds,
you will see all the records. Now, lets try to filter the records with ZoneType Big. To do this, add Related
Field Fixed type relation as follows:
Expand TruckTable, expand the relation ZonesTable which is
created in last step.
Right click on the node and click on New > Related field fixed.
You will see a relation like 0 == ZonesTable.??. This looks in the
way because, the related table is ZonesTable and we still didnt
bound any field so it looks like, ZonesTable.??. Finally, most of
the cases, we use Enums for filtering or categorizing the
records. So, an integer value is displayed, which can be updated
as per the requirement.
Here, the requirement is to display the Big zones. So, just
update ?? with ZoneType and value 0.
Open and check for the ZoneId lookup, you will see the lookup filtered as in the image.
To get a good understanding about Field Fixed relation, we will have 3 tables namely,
SmallZonesTable, MediumZonesTable and
BigZonesTable. Business requirement is as follows:
User will have a field called ZoneType in
TruckTable, which is an Enum and have the
values, Big, Medium and Small.
When Big is selected the records from
BigZonesTable should be displayed, for
Small, records from SmallZonesTable should
be fetched and similarly, from
MediumZonesTable for Medium i.e. the
parent tables values should be filtered
based on the current table filter.
Add an Enum called ZoneType to TruckTable
and create relations as shown in the
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 143
adjacent image.
The relations in the image BigZonesTable, MediumZonesTable and SmallZonesTable are new
relations created in TruckTable.
Each relation node has a normal relation and a Field Fixed relation as in the figure. When we
change the value of ZoneType in the current (TruckTable) table, the records are fetched
from corresponding tables.
For example, if we select the ZoneType value Big, the relation BigZonesTable will be used
and the normal relation TruckTable.ZoneId == BigZonesTable.ZoneId will fetch records from
BigZonesTable. Similarly, MediumZonesTable and SmallZonesTable relations will work.
In the above way, the Related field fixed and Field fixed relations work. Overall summary about
these 2 relation types is, Related field fixed works based on field value of related table i.e. the table
mentioned in the properties of relation, whereas the Field fixed works based on field value of current
table.
With enough explanation, its time to move to the next topic, DeleteActions.
Delete Actions
Let us consider a scenario, where a Zone record is deleted, which is not required further in the
Zones table. Now, there is a question, What should happen to the related records in TruckTable?. The
question is because, there is a related field in TruckTable and few records may be related to the deleted
record in Zones table, where the related records in TruckTable cannot be used anymore. The answer is
solved by DeleteActions. Let us consider an example where if you expand the DeleteActions node of
CustTable in AOT, you can see a delete action added with a name, CustTrans. Further, if you check the
properties of this delete action, you will see Table set to CustTrans and DeleteAction value is Restricted.
This indicates that if we try to delete a record in CustTable which has a related record in CustTrans, then
it will restrict us to delete the CustTable record. This will facilitate us to manage data consistency, as if
CustTable record is referenced in CustTrans and CustTable record is deleted, we may not find the link in
future which may cause problems. With this explanation, lets consider the scenario where the data in
TruckTable should be deleted when we delete record in Zones table. Lets create a delete action as
follows:
Expand Zones table. As we will delete a record and we like to update other table based
on the action done in Zones table, we will add DeleteAction in Zones table.
Right click on DeleteActions and click on New DeleteAction.
Set the properties of newly created DeleteAction, Table to TruckTable and DeleteAction
to Cascade and save the table. You will see a property called Relation which is used to
select the relation existing between two tables that will be used to match records in
related table for deletion.
Setting the DeleteAction property to Cascade will delete the related table records when
we delete the records in current table. Cascade extends the functionality of the table's
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 144
delete method [we will see the methods in coming section] which results, super(), in
delete, initiates a cascaded deletion that will follow the delete from table to table. A
cascaded delete is protected by tts internally which make sure that the database
changes arent committed until the entire transaction is complete.
The following table describes various DeleteActions available in AX 2012:
Delete Action Comments
None The delete action will be disabled when we use None for DeleteAction. This is
not used most of the cases except when we like to keep a DeleteAction that
should be disabled at some point of time for few days.
Cascade This delete action will extend the functionality of current table delete, which
will make the related table records gets deleted. The related records are also
deleted when we use this DeleteAction.
Restricted If we use Restricted, checking is done for existence of related records and if
there are any related records existing, deletion of current table record is
restricted. This will inform the user that there are related records in some
table and the current record cannot be deleted until the related record is
deleted.
Cascade + Restricted This is a different kind of action that works as Restricted when we try to
delete from Table or Form and works like Cascade when we try to delete
through X++ code. You should first delete the records in the related table
before deleting the primary record i.e. current table record. If the primary
record is being deleted as part of a cascading delete, the primary record and
the records in the related table will be deleted. We will understand this using
a scenario as follows:
Let us consider a scenario where there are 3 tables namely, BankAccounts
which will store account details, each account is given a debit card by default
which is stored in BankCard table and is related to BankAccounts. Finally,
there is one more table to handle transactions of the cards, which will be
taken care by CardTrans table. This table is related to BankCard. Its time to
discuss about the business rule where,
A BankCard should exist until transactions exist and cannot delete when
there are related records in CardTrans.
A BankCard can be deleted if there are no records in CardTrans.
If we delete BankAccounts record, it should delete BankCard and
CardTrans as we are deleting the primary record.
Based on the criteria, we will plan the delete actions for 3 tables as follows:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 145
Let us consider delete action Restricted in BankCard table with CardTrans
as we should not be able to delete the BankCard record if there is a
related record in CardTrans and Cascade delete action in BankAccounts
table with BankCard table as when we delete BankAccounts record,
everything should be deleted.
The above delete actions configured will cause a problem that, when we
try to delete BankAccounts record, it will check for BankCard which will
check for CardTrans and if there are any CardTrans records existing, we
will be restricted as BankCard will be restricted and ultimately,
BankAccounts record will be restricted which is a violation of business
rule.
To resolve this problem, we can use Cascade + Restricted in BankCard for
CardTrans and Cascade in BankAccounts for BankCard. Now, when we try
deleting a record in BankAccounts, it will trigger Cascade which will try to
delete BankCard record which will trigger Cascade on CardTrans, which
will ultimately delete the records in all tables.
If you try to delete records from BankCard and if there are related records
existing in CardTrans, an error is encountered which will follow business
rule.
Finally, with this sequence of delete actions, we will be able to follow all
the business rules mentioned above.
Summary is, when we use a Cascade + Restricted and try deleting the record
of primary table directly, if there are any records existing in related table, an
error is thrown acting as Restricted to delete records. If the same is triggered
indirectly, Cascade is triggered, which will delete the records in related table
also.
Hope, this clears the delete actions and all the queries on various types of
delete actions and I request you to mail me to get a practical example of same
scenario if you have any more queries on delete actions.
The main use of DeleteActions is, data integrity. When we delete a record in the parent table,
the transactions table should reflect accordingly, otherwise, we may see junk data which will not relate
to any master record in current and future which will create lot of confusions. This is the main use of
DeleteActions in AX.
Jobs Revisited
We can also perform CRUD operations on tables using X++ code. Examples in this section
demonstrate using X++ to perform CRUD operations on tables. We will have a full topic Queries to write
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 146
queries in X++ that fetch data. For time being to continue further topics, we will see a demonstration of
all the operations in the table below. Please note that we will come back to CRUD operation section
which will have an in-depth coverage of all the operations:
Operation Sample Program
Selecting records static void SelectionDemo(Args _args)
{
TruckTable truckTable;
//Select one record
select truckTable;
info(strFmt("%1, %2", truckTable.TruckId, truckTable.Description));
//Select a specific record
select truckTable where truckTable.TruckId == "T0003";
info(strFmt("%1, %2", truckTable.TruckId, truckTable.Description));
//Select specific fields
select TruckId, Description from truckTable;
info(strFmt("%1, %2", truckTable.TruckId, truckTable.Description));
//Select using aggregate functions
select count(TruckId) from truckTable;
info(strFmt("%1", truckTable.TruckId));
//Select all records one by one from the table
while select TruckId, Description from truckTable
{
info(strFmt("%1, %2", truckTable.TruckId, truckTable.Description));
}
//Select all records in a table matching the given criteria
while select TruckId, Description, TruckType from truckTable
where truckTable.TruckType == TruckType::Large
{
info(strFmt("%1, %2, %3", truckTable.TruckId, truckTable.Description,
truckTable.TruckType));
}
//Select all records in a table in reverse sorting
while select TruckId, Description, TruckType from truckTable
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 147
order by TruckId desc
{
info(strFmt("%1, %2, %3", truckTable.TruckId, truckTable.Description,
truckTable.TruckType));
}
//Select all records in a table with sorting options
while select count(TruckId), TruckType from truckTable
group by truckTable.TruckType
{
info(strFmt("%1, %2", truckTable.TruckId, truckTable.TruckType));
}
//Get a value from EDT array used as field in table
while select TruckId, Description, TruckDimensions from truckTable
order by TruckId desc
{
info(strFmt("%1, %2, %3", truckTable.TruckId, truckTable.Description,
truckTable.TruckDimensions[3]));
}
}
Note: All the above examples are based on the table TruckTable created in
previous sections, topics and exercises.
We have still more variations to see, which will be covered in Queries section.
Each statement will show you how to fetch records from table as per business
requirement. Please observe how the statements are written. We will see the
syntax with more variations in coming sections.
Insert records static void InsertRecords(Args _args)
{
TruckTable truckTable;
//Using insert() to insert records
truckTable.TruckId = "T0005";
truckTable.Description = "Suzuki";
truckTable.DateOfPurchase = today();
truckTable.TruckType = TruckType::Medium;
truckTable.ZoneId = "Z1001";
truckTable.ZoneType = ZoneType::Medium;
truckTable.TruckDimensions[1] = 6;
truckTable.TruckDimensions[2] = 7;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 148
truckTable.TruckDimensions[3] = 140;
truckTable.TruckDimensions[4] = 150;
truckTable.insert();
//Using doInsert() to insert records
truckTable.TruckId = "T0006";
truckTable.Description = "Yamaha";
truckTable.DateOfPurchase = today();
truckTable.TruckType = TruckType::Large;
truckTable.ZoneId = "Z1002";
truckTable.ZoneType = ZoneType::Small;
truckTable.TruckDimensions[1] = 7;
truckTable.TruckDimensions[2] = 8;
truckTable.TruckDimensions[3] = 130;
truckTable.TruckDimensions[4] = 140;
truckTable.doInsert();
}
Note: We insert a record by creating a variable of table type, assign values to
fields and use either insert () or doInsert (). We will see the difference in
coming sections about the methods, for time being, please understand that
both will serve the same purpose.
Update records static void UpdateRecords(Args _args)
{
TruckTable truckTable;
//Update a record using update()
ttsBegin;
select forUpdate truckTable where truckTable.TruckId == "T0005";
truckTable.TruckDimensions[1] = 10;
truckTable.TruckDimensions[2] = 10;
truckTable.TruckDimensions[3] = 100;
truckTable.TruckDimensions[4] = 100;
truckTable.TruckType = TruckType::Large;
truckTable.update();
ttsCommit;
//Update a record using doUpdate()
ttsBegin;
select forUpdate truckTable where truckTable.TruckId == "T0006";
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 149
truckTable.TruckDimensions[1] = 3;
truckTable.TruckDimensions[2] = 3;
truckTable.TruckDimensions[3] = 50;
truckTable.TruckDimensions[4] = 60;
truckTable.TruckType = TruckType::Small;
truckTable.doUpdate();
ttsCommit;
}
Note: We update a record by selecting the record using table type variable,
assign values to fields we like to update and use either update() or doUpdate
(). We will see the difference in coming sections about these methods, for
time being, please understand that both will serve the same purpose. You can
see few statements like ttsBegin, ttsCommit and forUpdate keywords which
are used for Transaction Tracking System. We will discuss more about this in
coming sections.
Delete records static void DeleteRecords(Args _args)
{
TruckTable truckTable;
//Delete using delete()
ttsBegin;
select forUpdate truckTable where truckTable.TruckId == "T0005";
truckTable.delete();
ttsCommit;
//Delete using doDelete()
ttsBegin;
select forUpdate truckTable where truckTable.TruckId == "T0006";
truckTable.doDelete();
ttsCommit;
}
Note: We delete a record by selecting the record using table type variable,
and use either delete () or doDelete (). We will see the difference in coming
sections about the methods, for time being, please understand that both will
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 150
serve the same purpose. You can see few statements (probably keywords) like
ttsBegin, ttsCommit and forUpdate keyword which are used for Transaction
Tracking System. We will discuss more about this in coming sections.
Table Methods
Let us consider a scenario where you like to change a field value when another field value
changes in a table, or create a record in related table when you create record in some table. There is
concept called as Triggers, which execute automatically when an action is performed on database
tables in SQL/PL SQL. But, while working with AX, it is not required for us to open the physical database
and write triggers and even it is not recommended as we are not sure getting expected results. Due to
this reason, we need a facility to get notifications/events mechanism in AX which is a must, while
working with objects like Tables especially. To solve this requirement, every table created in AX has a
node called Methods. This node is used to create/override methods in AX tables which will get executed
/ triggered when relevant operation is done on particular table. Though the name is methods, these
methods can be treated as events or triggers of AX tables as they get executed when we perform
operation on table automatically. We will see how these methods are used and how they work in
current text. Following are a few points about the Methods in tables:
Methods are used to add X++ code to the application.
We write the business logic required to the application in methods. We can find
methods not only in tables, but also in classes, queries and various objects will be
covered in related topics.
We have a large number of table methods available which will execute when we do the
operations like update, insert, delete, edit etc. on the table. These methods are default
methods.
You can add your own methods which can be called from other methods and use them.
To write or override a default method, follow the steps mentioned below:
Select the table you like to override the methods.
Expand the table and right click on methods node.
Select Override methods and click on the method you like to override. You will see
X++ editor with the method syntax. Add the business logic accordingly.
To add a new method, right click methods node of table and click on New Method,
you will see the X++ editor with a new method created. Rename the method as per
requirement and add the business logic accordingly.
With enough information about methods, we will write few methods to understand the working
of methods on tables. Let us consider a scenario where we like to validate the field TruckId value of
table TruckTable which should be at least 4 characters long. To check this condition, we need an event
when we try to update the field value and this event should validate the field value. The method used to
do this operation is, validateField(). Lets understand a sample how to validate field value using this
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 151
method. Right click on TruckTable, override the method validateField(). Following is the syntax of
validateField() method:
public boolean validateField(FieldId _fieldIdToCheck)
{
boolean ret;
ret = super(_fieldIdToCheck);
return ret;
}
In the above method, we are able to see a call to super() method which will execute the code
from the corresponding method in the parent class. When overriding a default table method you inherit
a method from a system class called XRecord. You will understand more about inheritance and super() in
Object oriented programming. Methods are used to add logic to the objects like forms and reports in AX.
There may be numerous situations to add logic to various objects in AX which cannot be avoided. Now,
lets add code for validating the field value as follows:
public boolean validateField(FieldId _fieldIdToCheck)
{
boolean ret;
ret = super(_fieldIdToCheck);
if(_fieldIdToCheck == fieldNum(TruckTable, TruckId))
{
if(strLen(this.TruckId) <= 3)
{
ret = ret && checkFailed("TruckId should be greater than 4 characters");
}
}
return ret;
}
In the above method, we are checking the field id with the required field using the method
fieldNum(), which will return the field id of a table field. When particular field is edited, this method will
be called with that field id and the business logic in if condition will check for the actual requirement and
throw error based on the condition evaluated. Note that, we are using this to access the value of field.
The operator this refers to current record which is pointed. You can get more information about this in
Object oriented programming. This is very simple example that validates only one field in the table. Let
us consider a scenario, where we need to validate multiple fields. To validate multiple fields, we dont
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 152
have a method for each field in table, instead, we use the same method to validate any number of fields
as follows. Note that, there is no business logic in the following program and just to understand how
multiple fields can be validated:
public boolean validateField(FieldId _fieldIdToCheck)
{
boolean ret;
ret = super(_fieldIdToCheck);
if(_fieldIdToCheck == fieldNum(TruckTable, TruckId))
{
//Validation condition
}
else if(_fieldIdToCheck == fieldNum(TruckTable, Description))
{
//Validation condition
}
return ret;
}
Note: It is recommended to use switch-case to check for field ids if there are multiple fields to
be validated instead of using if - else if - else ladder.
Finally, in the same way, we will try to validate the dimensions of the TruckTable, which is an
array. The business requirement is, any truck will have some dimensions which will be a positive and
non-zero value. We will try a different approach than the above one as follows, as this is not a single
values field but an EDT array:
public boolean validateField(FieldId _fieldIdToCheck)
{
boolean ret;
ret = super(_fieldIdToCheck);
switch(fieldId2name(tableName2id("TruckTable"), _fieldIdToCheck))
{
case "TruckDimensions[1]":
if(this.TruckDimensions[1] <=0)
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 153
ret = ret && checkFailed("TruckDimension value cannot be less than or equals to 0");
}
break;
case "TruckDimensions[2]":
if(this.TruckDimensions[2] <=0)
{
ret = ret && checkFailed("TruckDimension value cannot be less than or equals to 0");
}
break;
case "TruckDimensions[3]":
if(this.TruckDimensions[3] <=0)
{
ret = ret && checkFailed("TruckDimension value cannot be less than or equals to 0");
}
break;
case "TruckDimensions[4]":
if(this.TruckDimensions[4] <=0)
{
ret = ret && checkFailed("TruckDimension value cannot be less than or equals to 0");
}
break;
}
return ret;
}
In the above method, we are checking with field name instead of field id. To get the field name,
we used fieldId2name(tableName2id("TruckTable"), _fieldIdToCheck). I leave understanding of above
program to you. Please note how we tried to validate multiple fields. You can add more fields to the
same switch which are discussed in this section to validate more fields.
In addition to the validateField, a new method is given in AX 2012, validateFieldValue(). As this
method is not there in AX 2009, the method validateField() is used for validating the fields value. The
syntax is as follows:
public boolean validateFieldValue(FieldName _fieldName, int _arrayIndex = 1)
{
boolean ret;
ret = super(_fieldName, _arrayIndex);
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 154
return ret;
}
The above method serves same purpose as validateField(). We can validate the value of field
using the above method. If you observe the syntax of above method, we are able to see field name and
array index getting passed which can be used when we have array fields in table. These will make us to
write the code still easier than the complex approach which we used previously in validateField()
method.
Consider a scenario where we have a requirement to update a field value based on updation of
another field. validateField() is used to validate the value of field and not for the said purpose. Instead,
we have methods, modifiedField() and modifiedFieldValue() in table which will get executed when a
field value is modified successfully. Note that, modifiedFieldValue() is added in AX 2012, which was not
there in AX 2009. Lets see a sample with the scenario, if either of truck dimensions are less than or
equal to 3, the truck type is small. If they are in between 3 and 6, size is medium otherwise, large. We
will use modifiedFieldValue() for doing this operation. I request you to simulate the same using
modifiedField() to understand the functionality.
public void modifiedFieldValue(FieldName _fieldName, int _arrayIndex = 1)
{
super(_fieldName, _arrayIndex);
if(_fieldName == "TruckDimensions")
{
if(this.TruckDimensions[1] <= 3 || this.TruckDimensions[2] <= 3)
{
this.TruckType = TruckType::Small;
}
else if(this.TruckDimensions[1] <= 6 || this.TruckDimensions[2] <= 6)
{
this.TruckType = TruckType::Medium;
}
else
{
this.TruckType = TruckType::Large;
}
}
}
In the above program, we are checking for the field name and updating TruckType field value of
TruckTable based on the value of TruckDimensions field. Now, we will see an example that will update
the field DateOfPurchase to current days date. This may be modified by the user at later point when
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 155
required but we plan that to the current date when a record is created. If you like to get the original
value of the field, you can use this.orig().TruckType, which will return the value of original value of the
field. The method orig() will return original record before the field value is modified. Note that you can
use orig() to access original record and fields values till the record is not saved. Orig() will return an
instance of the current table. A single field value from orig() is retained by specifying the field in our
example. When the record is committed orig() will be updated. To do this, we use the method called
initValue() as follows:
public void initValue()
{
super();
this.DateOfPurchase = today();
}
We are initializing value to only one field in the above example but can initialize values to
multiple fields based on criteria. Here onwards, when we create a new record in table TruckTable, it will
initialize the DateOfPurchase field value with the current date.
In addition to the above discussed methods, there are huge number of event methods available
on tables for doing various CRUD and validation operations . Once after validation and modifying of a
field is done, when you save the record, the method validateWrite() is called. This method serves the
validation purpose for the record and indicates whether data is valid and ready to be written.
validateField() checks at field level where as validateWrite() will check for record level. You can override
this method if you like to check whether value is inserted or not into description of ZonesTable. Override
validateWrite() method on ZonesTable as follows:
public boolean validateWrite()
{
boolean ret;
ret = super();
if(!this.Description)
{
ret = ret && checkFailed("Description should not be empty.");
}
return ret;
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 156
The above method will check the description of the current record. If the description is empty, it
will throw an error disallowing to save the record until the condition fails i.e. some value is entered into
description. Even if you try to close the table browser it will throw the same error as it will try to save
the record when we try to close the table browser. Instead, you can press Esc key to close the table
browser without saving record.
In addition to validateWrite(), one more validation method validateDelete() can be used to find
whether the record can be deleted() or not. We can add validations at record level to find whether the
record can be deleted or not. When you delete a record, a warning dialog is displayed to get
confirmation whether to delete the record from the table. For example, let us consider a business
scenario where large trucks cannot be deleted as follows:
public boolean validateDelete()
{
boolean ret;
ret = super();
if(this.TruckType == TruckType::Large)
{
ret = ret && checkFailed("Large trucks cannot be deleted.");
}
return ret;
}
The above method will return false if the TruckType is Large which will not allow us to delete the
record. If the condition fails and it returns true, delete() of table gets executed and the record gets
deleted. You can also override delete() to perform any operations while deleting the record. An example
will be, while deleting a record, few times we may not create a DeleteAction due to a fact that the
deletion on related table should be done based on some criteria. In this case, we write a delete
operation on related table explicitly and delete records based on the criteria in your particular business
scenario and override delete() method for doing this operation.
Note: All the validate methods returns boolean value which will confirm whether to proceed to the next
phase or not. Next phase stands for the modification/insertion/updation of record/field i.e. for instance,
call insert()/update() etc. methods after validation is done.
The methods which does insertion or updation to the table are insert() and update(). One of
these methods will execute in specific scenarios when validateWrite() returns true. When we create a
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 157
new record and try to save, insert() method is called and when we modify an existing record and try to
save the record, this will call update() method of table.
Both insert() and update() have their own advantages. The insert method generates values for
RecId and system fields, and then inserts the contents of the buffer into the database. Let us consider a
scenario where there are 2 tables BankAccounts and BankCard respectively. The business case is,
whenever a BankAccounts is created, a BankCard record should be created automatically. To get this
functionality, we override the insert() method of BankAccounts table and insert a record into BankCard
table through X++ code as follows:
public void insert()
{
BankCard bankCard;
super();
bankCard.AccountNum = this.AccountNum;
bankCard.Description = "Card issued.";
bankCard.CardNumber = 0000-0000-0000-0000;
if(bankCard.validateWrite())
{
bankCard.insert();
}
}
In the previous examples of inserting a record, we never called validateWrite() as we are not up
to the table methods while we discussed the topic. If we call insert() directly without calling
validateWrite() the validations are not done. So, it is recommended to call this method explicitly when
you write X++ code to insert a record. Like insert(), update() can be used for same kind of scenarios
where we update the record of one table when the current table record is updated. Please note that
these are some cases when we can use the methods and I request you to read this multiple times on
when the methods will get executed but not stick to examples.
Finally, we have few more methods started with AOS, like aosValidateInsert(),
aosValidateUpdate() etc. These AOS methods are called when particular methods are called i.e. super()
in update() will call aosValidateUpdate() and super() in insert() will call aosValidateInsert() respectively.
In the examples of inserting records discussed in previous sections, we can insert records in 2
ways, insert() and doInsert(). When we use doInsert(), insert() method call is bypassed i.e. the call is not
made on the table. So whatever we write in insert() method is not executed when we use doInsert() to
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 158
insert a record. But, even in this case, aosValidateInsert() will be called. The same is the case with
update() and delete(). All the methods discussed till now are the system methods. It is recommended to
write few application methods, find() and exist(). The method find()is used to find and return a record of
the table using a key and exist() will check whether a record is existing in the table or not. Both the
methods should be static. You will understand more about static in Object oriented programming
section. Lets see an example of find method as follows:
static TruckTable find(TruckId _truckId,
boolean _forUpdate = false)
{
TruckTable truckTable;
;
if (_truckId)
{
select firstOnly truckTable
where truckTable.TruckId == _truckId;
if (_forUpdate)
truckTable.selectForUpdate(_forUpdate);
}
return truckTable;
}
exist() method looks as follows:
public static boolean exist(TruckId _truckId)
{
boolean found;
;
found = (select firstOnly
RecId
from
truckTable
where
truckTable.TruckId == _truckId).RecId != 0;
return found;
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 159
Finally, we can add new methods when we require to do any operations or get/set the data as
follows:
public TruckType findTruckType(TruckId _truckId)
{
TruckTable truckTable;
;
if (_truckId)
{
select TruckType from truckTable
where truckTable.TruckId == _truckId;
}
return truckTable.TruckType;
}
The above method will find the TruckType for the given truckId. This method will be called from
another method or some executable code. This method is an application method written by developer
and is not called by AX directly as this is not related to any of the events.
There is a sequence of methods getting called, one by one. For example, when we try to insert a
new record, first initValue() and for each field, validateField() and modifiedField() and finally
validateWrite() and insert() will get executed. The sequence depends on operations we do on the table.
We see more about this sequence once we are into development of forms. To find what methods are
called when, I request you to write an info() before super() for each method in table and do CRUD
operations on the table to find the exact method calling sequence. There are a few points to note with
the methods as follows:
super() call in each table method will call the respective parent methods which will
perform relevant operation. For example, the validateWrite() check for the mandatory
fields at record level and will throw error if there are any mandatory fields blank without
values. If you comment super() call in these methods, you may get unexpected results.
Few times, we may comment the super() purposefully to suppress the default
functionality. This should be done based on business scenario.
You can write your X++ code before and after super(). The results may differ in both the
situations. The following sample will explain you what is the difference if you write
before super() and after super():
After super() call Before super() call
public boolean validateWrite()
{
public boolean validateWrite()
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 160
boolean ret;
ret = super();
if(!this.Description)
{
ret = ret && checkFailed("Description
should not be empty.");
}
return ret;
}
boolean ret;
if(!this.Description)
{
ret = ret && checkFailed("Description
should not be empty.");
}
ret = super();
return ret;
}
Let us consider, super() returns false in this case
and value of field description is empty. Now, if
condition evaluates to true as description is
empty and ret will become false due to AND
with checkFailed(), which will not allow to
update the record as validateWrite() return
false, which is value in ret. This is an expected
result.
Consider a case where super() will return true
and value of field description is empty. Now, if
condition evaluates to true as description is
empty and ret will become false due to AND
with checkFailed(), which will not allow to
update the record as validateWrite() return
false, which is value in ret. This is an expected
result.
Let us consider, super() returns false in this case
and value of field description is empty. If
condition evaluates to true as description is
empty and ret will become false due to AND
with checkFailed().Later, the super() will
override ret value as your code is before super()
and return false, which will not allow to update
the record as validateWrite() return false, which
is value in ret. This is an expected result.
Consider a case where super() will return true
and value of field description is empty. Now, if
condition evaluates to true as description is
empty and ret will become false due to AND
with checkFailed(). Here , ret will have the value
false but super() call will assign true to ret as
this statement is before super() call which will
allow to update the record as validateWrite()
return true, which is value in ret. This is an
unexpected situation which will break your
business case. So, it is recommended to
consider writing code before or after super()
based on situation you are in.
Note that this is just an example to show the
unexpected situations. We may write before
and after super() few times to get expected
result.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 161
In addition to the table methods, we have various methods in AX forms which will call
the table level methods also. We will see the advantages of form methods as well as
where to write the code at particular situations.
With enough explanation on table methods, its time to move to next topic, Transaction
Tracking System, which is used for managing transactions on tables when we perform CRUD operations.
Transaction Tracking System
If you remember the scenario of BankAccounts table and BankCard table, a record should be
inserted into BankCard table whenever we insert a record into BankAccounts table. Here, there is a
business case or what we can call is, validation which we didnt discussed at that place. The core
validation required is, either the records should get inserted successfully into both tables or fail in both
tables but not a case like insert is done in one table and fail in other. Consider one more case where we
are adding amount in one table and deducting in another like, credit card processing. In this case also,
the operation should be done successfully either in both tables or fail in both tables. This is called as
transaction.
A transaction is a collection/group of one or more SQL statements against one or more tables. It
can contain one or many operations that might insert, update, delete or select data. These statements
are formed into one logical group and are called as transaction. The scope of transaction started from
begin statement to commit statement. Following are few properties of transactions, often called as ACID
properties:
Atomicity-All or nothing. When you commit the transaction, either all statements commit or
none of them commit i.e. If anyone of the operation fails, complete transaction will fail.
Consistency - The database must be in a consistent or legal state before and after the
database transaction. It means that a database transaction must not break the database
integrity constraints.
Isolation - Data used during the execution of a database transaction must not be used by
another database transaction until the execution is completed. Therefore, the partial results
of an incomplete transaction must not be usable for other transactions until the transaction
is successfully committed. It also means that the execution of a transaction is not affected
by the database operations of other concurrent transactions.
Durability - All the database modifications of a transaction will be made permanent even if a
system failure occurs after the transaction has been completed.
Consider the following situation, amount should be transferred from one account to another
account:
Decrement amount in account1.
Increment amount in account2.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 162
The following example shows this:
static void postTransaction(Args _args)
{
BankAccounts bankAccounts;
ttsBegin;
bankAccounts = BankAccounts::find("SB00001", true);
bankAccounts.Amount -= 1000;
bankAccounts.update();
bankAccounts = BankAccounts::find("SB00002", true);
bankAccounts.Amount += 1000;
bankAccounts.update();
ttsCommit;
}
Note that account ids and amounts are hardcoded in this example. This approach is not
recommended. Instead, I request you to write this as a table method which accept the account ids and
amount as arguments.
In the above example, to start the transaction, we used the ttsBegin statement and to save the
transaction, ttsCommit is used. Transaction Tracking System (TTS) secures that all database operations
within TTS statements are committed entirely or not.
Every time you perform an insert/ update/delete operation on table(s), you should use the TTS.
Honestly, you cannot update record without using TTS statements in AX. AX will throw an error if you
dont select a record for update and if an update is not in TTS statements. A TTS loop is initialized by
using the keyword ttsBegin and the keyword ttsCommit will write all the data operations within the TTS
statements to the database. We can abort a transaction using ttsAbort manually. If the system crashes
unexpectedly or due to an exception, ttsAbort is automatically invoked by TTS. If your code is called by
another method, it may not be required for you to write ttsBegin as the previous method may have used
ttsBegin and if not, you have to start the TTS loop or you may get error. Each time you use ttsBegin, one
level of TTS will get increased. Each level must be committed with ttsCommit which may result error if
you forget this i.e. number of ttsCommit statements in your application should be equal to number of
ttsBegin statements.
If you observe the above example, we are fetching the record with flag true for update i.e. the
record fetched should be updatable and the call for updation should be in between ttsBegin and
ttsCommit statements. We can also select the record manually as in the following example:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 163
static void postTransaction(Args _args)
{
BankAccounts bankAccounts;
ttsBegin;
select forUpdate bankAccounts where bankAccounts.AccountNum == "SB00001;
bankAccounts.Amount -= 1000;
bankAccounts.update();
select forUpdate bankAccounts where bankAccounts.AccountNum == "SB00002";
bankAccounts.Amount += 1000;
bankAccounts.update();
ttsCommit;
}
As explained, note that the select forUpdate statement is used in between ttsBegin and
ttsCommit. You may get errors or unexpected results if you make this mistake, which is very common
for developers. Finally, the update() is called for every table before commit is called.
With a complete and enough explanation about transaction tracking system, its time to move
forward one more step, Table Inheritance.
Table Inheritance
Table inheritance is the property that allows a table to inherit the behavior (constraints, storage
options, methods) from the super table above it in the table hierarchy. A table hierarchy is the
relationship that you can define among tables in which sub tables inherit the behavior of super tables.
When designing a database, we sometimes come across situations where there are multiple types of
entities that we are modeling, but we'd like them to all have certain attributes or relations in common.
Using "sub-type" tables is a simple way to implement table inheritance in SQL Server. Following are few
advantages of table inheritance:
It encourages modular implementation of your data model.
It ensures consistent reuse of schema components.
It allows you to construct queries whose scope can be some or all of the tables in the
table hierarchy.
In a table hierarchy, a sub table automatically inherits the following properties from its super
table:
All constraint definitions (primary key, unique, and referential constraints)
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 164
Methods
Indexes
Accessing
Table inheritance is introduced in AX 2012. When you inherit a parent table in to child table, the
table structure in physical database is not changed. Only AX will control all the operations related to
table inheritance. In the terminology for table inheritance, we say that the derived table extends its base
table. But we never use the terms parent or child to explain tables in an inheritance relationship in table
inheritance. The terms parent and child can be used to describe foreign key relationships between
tables. Note that for time being and better understandability, we will use the terminology super table
and child table until this topic is completed. The following is an example where we may need table
inheritance and table inheritance can be applied:
Table schema for BankAccounts table Table schema of BankCard table
AccountNum
Name
AmountAvailable
DateOfCreation
AccountNum
CardNumber
AmountAvailable
DateOfCreation
CardLimit
Pin
Following are the reasons, why we can use table inheritance in the above case for the two tables
mentioned:
a. If we observe above schemas, both needs same fields and the child table needs few
more fields.
b. Both tables should exist for different purposes as per business requirement.
c. Both tables have only 1:1 relation i.e. a record in BankAccounts table will have only one
related record in BankCard table. As per business scenario, each bank account will be
issued only one card.
d. The record in both tables refer to only one record in real world, the bank account of a
person
e. Until the bank account record is there, bank card record will exist as per business
scenario.
From the above observations, we can come to a conclusion that the fields which are there in
parent table are needed for child table and can be related. To do this, we can have a PK-FK relationship
using table relations or we can use table inheritance. In the current example we will use table
inheritance instead of PK-FK due to the advantages which will be discussed in this text. To use the table
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 165
inheritance feature, follow the steps below. This example will create table from scratch for clear
explanation:
Create a table named BankAccounts. This is our parent table.
Set the SupportInheritance property of BankAccounts to Yes.
Create a field in the BankAccounts table of type Int64 and name it InstanceRelationType.
Set the property InstanceRelationType of table BankAccounts to the newly created field
InstanceRelationType.
Now, add the fields AccountNum, Name, AmountAvailable and DateOfCreation to the
table BankAccounts. You can create new EDTs as per your requirement but for the
current requirement, it is not required as we can reuse existing EDTs.
Create a table named BankCard. This is our child table.
Set the properties SupportInheritance to Yes and Extends to BankAccounts of table
BankCard to inherit from BankAccounts.
Add the fields CardNumber, CardLimit and Pin to the BankCard table. Dont add the
fields AccountNum, AmountAvailable and DateOfCreation once again as they will be
inherited into BankCard table from BankAccounts table.
Create the required Indexes and validations using methods etc. This example is purely to
demonstrate the table inheritance and we are not following the rules and best practices
of creating indexes etc.
Thats done, parent and child table are created. Now, open the BankAccounts and
BankCard tables in table browser. You will see the following in table browser:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 166
As you can see in the above images, the BankAccounts table shows its own fields where
as the BankCard table shows all the fields of BankAccounts as well BankCard tables. This
is how table inheritance works.
Check creating index, you will see that index is also inherited, but note that you cannot
change primary index of parent table once it participates in inheritance i.e. it is inherited
by some child table.
Following are few properties and behavior of table inheritance:
Insertion of record in child table will insert record into concerned fields in parent table.
This will even call the method of insert() in the table BankCard first and from that call, it
will call insert() of table BankAccounts. You can observe the stack trace in below image:
Please note the RecId which is same in parent and child tables. This is one of the keys to
identify the record.
Few people used to get confused with a query, How and why a record is created in
parent when a record is created in child? Properties flow from parent to child but not to
parent know? This is what their question is. Question is pretty straight. Answer is, treat
table as class and record as instance. An instance of class gets created when constructor
is called. Now, as per Object orientation rule, a child class object will have a parent
object in it internally. When you call child constructor, it will call the parent constructor
using super() call. And, from the above stack trace, insert() of child table is calling
insert() of parent table. This will ultimately insert record in parent table when a record is
inserted into child table. Hope, this clears your query. Still if you have query, please
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 167
refer Object oriented programming section of this book. The same is the way update
works.
Please check the following program:
static void FetchFromBankCard(Args _args)
{
BankCard bankCard;
while select bankCard
{
info(strFmt("Account Num: %1", bankCard.AccountNum));
info(strFmt("Name: %1", bankCard.Name));
info(strFmt("Date Of Creation: %1", bankCard.DateOfCreation));
info(strFmt("Amount Available: %1", bankCard.AmountAvailable));
info(strFmt("Card Number: %1", bankCard.CardNumber));
info(strFmt("Card Limit: %1", bankCard.CardLimit));
info(strFmt("Pin: %1", bankCard.Pin));
}
}
In the above program, bankCard variable is able to select the values of BankAccounts
table also. In this way, when we select child table fields, it will fetch parent table fields
also based on criteria you select. Note that, here we have selected all the fields.
Finally, check deleting a record in child table, you should see that the parent table
record is deleted.
When to use Table Inheritance
There should be no one to many or many to many relationship exists between the
two tables i.e. parent and child tables.
The record base table and corresponding record in derived table should refer to the
same item in real world having different fields or attributes.
Each record in the base table should have only one corresponding record in derived
table. Deletion of a record in either table should delete in the other to maintain
consistency.
An item represented in base table should not be represented in more than one of its
derived tables.
Same field names are a clue to plan table inheritance, but dont plan table inheritance
based only on this clue.
Finally, child table is not meant for performance improvement of physical database.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 168
Tables in table inheritance can be classified into 2 types, abstract and concrete. This is classified
using the table property Abstract, which will be set to either Yes or No. Records can be created only for
concrete table and an attempt to create and insert records in abstract table will throw a run-time error.
A table can be made as abstract table at any level without any restriction on the level.
Disadvantages of table inheritance
Table inheritance is a bit costlier to implement in AX as the structure and
implementation is not maintained in physical database which can be found and
understood from the adjacent image. This figure is the physical structure of tables in
physical database. AX will maintain the logic related to do this and all the logic of CRUD
operations is maintained by AX.
As the code is called from child to parent, a
lot of care should be taken while using
table inheritance.
Multiple levels of inheritance may cause
performance drop due to load on AX.
There are few restrictions like, primary
index cannot be updated once a table
participates in table inheritance, which
should be taken care while developing with
table inheritance as once development is
done and updation of these features may
require lot of effort.
With enough information about the tables and all
the nodes, possibilities on tables, its time to move to few
advanced topics.
Maps
Consider a scenario where you have 2 tables, CustTable and VendTable with similar fields like
account number, group, invoice account etc. Now, with enough experience working with AX, its time
for you to create/reuse existing tables and write code to do some operation in common like displaying
or invoicing on those 2 tables i.e. on CustTable and VendTable. As discussed, both the tables will have
mostly common fields. Before writing common operation, you have to consider the following points:
Method overloading is not available in AX. You get an error if you try method
overloading. Please check Object oriented programming to know more about
overloading and overriding.
If you create a method and pass VendTable variable into CustTable variable, you get an
error as both are different types.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 169
From the above 2 points, you have following options:
Create 2 methods, one for CustTable and one for VendTable
Use an existing functionality in AX which can provide polymorphism.
The second option will be better which will reduce code duplication. We achieve this
using a feature called as Maps, which will be discussed in this section.
Maps are used to link tables. By creating a map, fields can be accessed in different tables if the
fields are of similar types but have different names. Maps define X++ elements that wrap table objects
at run time. With a map, you associate a map field with a field in one or more tables. This enables you to
use the same field name to access fields with different names in different tables. For example, for a class
that implements functionality to process data in tables that have common fields and having common
operations, we can create a Map and use that to declare arguments which can be used to accept
different tables variables as parameters.
Maps do have methods. Map methods enable you to create or modify methods that act on the
map fields. A table can be accessed through more than one map based on business requirement. If more
than one map accesses the same table, each map accesses different subsets of fields in the table. Maps
don't define database objects and so they aren't synchronized with the database.
The core uses of maps include:
Simplicity - maps provide a single interface to use the fields in multiple tables. It means that the
object that references the map field can be used against multiple tables without changing the
field names.
Consistency - table fields with different names in tables can be accessed in code in a consistent
manner i.e. if a field is named CustAcc, CustAccount, CustomerId in each table, we can access
using the name AccountId, which will be created in Map and can be used in common for all
others.
Code reuse We can write methods in map which enables you to add code that runs against the
map fields of different tables that reduces code redundancy in different tables. We can use the
same map method to act on different tables fields.
Each Map in AOT will have 4 nodes as follows:
Fields: This node will have the field elements of the map you create. Fields you create must
have the same data type as the field of table to which it is associated. You can use the
ExtendedDataType property of the map field to specify the data type of the map field if the
map field is associated with a table field that is based on an extended data type.
FieldGroups: FieldGroups node contains fields that are logically related. Field groups in map
will work in the same way as they work in tables. You can refer to table field groups for
more information.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 170
Mappings: This node is the place where the map fields are associated with table fields. The
map combines tables under this node. Each table of this node will have the table fields
associated with the map fields. If you dont have any association available for a map field
with a table field, you can leave that blank without associating to any table field by setting
MapFieldTo property blank.
Methods: You can write methods on this node that will act on the tables mapped in Map.
There are few system methods defined on maps which are derived from xRecord class.
With enough information about maps, its time see an example of creating and using a map. To
do this exercise, we dont create any tables, we will use the existing tables, CustTable and VendTable
and find few required fields as follows:
I request you to open and take a look at the existing tables CustTable and VendTable in AOT.
Find few common fields and their EDTs. You can find the EDT of them in properties of fields. If the EDTs
of both dont match, please check if they are inherited from another EDT. For example, AccountNum of
CustTable has EDT CustAccount whereas AccountNum of VendTable has VendAccount. Check extends
property of CustAccount and VendAccount EDTs, you will find CustVendAC to both of them in common.
Following are the fields we will use to create our map:
Field Name in CustTable/VendTable EDT in common, which we use in map
AccountNum CustVendAC
BankAccount BankThirdPartyAccountId
ContactPersonId ContactPersonId
CreditMax CreditMaxMST
Currency CurrencyCode
InvoiceAccount CustVendAC
Now, follow the below steps to create a map as discussed above:
Right click on Maps and click New > Map. Rename the newly created map to CustVendMap.
Drag the extended data types discussed in above table to the fields node of newly created
map i.e. CustVendMap. You can also create a new field of required type and set the EDT of
the field under fields node of map.
Change names of the fields accordingly as per your naming conventions and business
requirement.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 171
Now, we will map the fields to the tables CustTable and VendTable. To do this, right click on
Mappings node, click New Mapping. Select the CustTable for the Mapping Table property in
the properties of mapping just created.
Expand the CustTable mapping, created in last step, you will see the list of fields. Now, you
have to map these to table fields. To do this, set the property MapFieldTo of each field to
the corresponding table field. For example, set the property MapFieldTo of AccountNum
property to AccountNum. You will
see a mapping created immediately
which looks like,
CustVendMap.AccountNum ==
CustTable.AccountNum. Repeat this
step until all fields are mapped. Note
that you can leave the field blank if
you dont have anything to map,
which may be used for other tables
planned in map.
Repeat the mapping for VendTable
with all the steps.
Once everything is done, your map
is ready, which looks as in the
adjacent figure.
If you modify or update the fields of any of the tables in the map, you have to restore the
map for reflecting the changes to the map. Each time a change is made to the fields in map,
you will have to restore the map otherwise changes will not be shown in mappings before
restarting the client.
Now, lets write a method that will use the map. The method will display all the records
available in concerned table. You can add this method in either table or map or in any of the relevant
classes etc. for time being, Im writing the method in ZonesTable which is created by us in previous
exercises. The method is as follows:
void listRecords(MyMap _myMap)
{
;
while select _myMap
{
info(strFmt("%1, %2", _myMap.accountNum, _myMap.currency));
}
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 172
The following job will explain us calling the above method:
static void TestCustVendMap(Args _args)
{
CustTable custTable;
VendTable vendTable;
ZonesTable zonesTable;
;
zonesTable.listRecords(custTable);
zonesTable.listRecords(vendTable);
}
The following behavior of maps can be observed from the above job:
We are able to pass custTable and vendTable, which are different. This will reduce our code.
For example, there is a requirement to find the existence of sales orders or purchase orders
for customer/vendor which may need 2 methods one on SalesTable and one in PurchTable,
which can be done using only one method in map as follows:
public boolean existOrders(CustVendMap custVendMap)
{
SalesTable salesTable;
PurchTable purchTable;
switch(this.TableId)
{
case tableNum(CustTable):
select count(RecId) from SalesTable
where salesTable.CustAccount == custVendMap.AccountNum;
if(salesTable.RecId >= 1)
{
return true;
}
case tableNum(PurchTable):
select count(RecId) from purchTable
where purchTable.OrderAccount == custVendMap.AccountNum;
if(purchTable.RecId >= 1)
{
return true;
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 173
}
return false;
}
The above method will check if there are any orders existing and return true or false based
on the availability. This method can be called using the map as in the following example:
static void TestMapMethod(Args _args)
{
CustVendMap custVendMap;
CustTable custTable;
VendTable vendTable;
;
select custTable where custTable.AccountNum == "Cust0001";
custVendMap.existOrders(custTable);
select vendTable where vendTable.AccountNum == "Vend0001";
custVendMap.existOrders(vendTable);
}
Modify and update the method with adding your ideas to that.
Following are few important points that can be noted on maps:
Map is declared just as a table and only the name shows that a map is used. Even the icon
while declaring the map looks like table icon.
Suffixing the name of the map with *Map makes it easier to identify and read the code of
map.
Note that a map will not contain any records and can be considered as a specialized
Common table. The map can be initialized with any of the tables declared in the map.
There are system methods in map which are very similar to those in table. You can initialize
fields, check modified fields and validate before saving or deleting the records on the table
using the map. Calling a default method like insert() on a map will execute the
corresponding insert() on the mapped table.
You can write your own methods in maps and tables. However, you must make sure the
name of the method is the same for all mapped tables, to avoid run time errors.
With enough information on maps, we will move to the next topic, views as follows.
Views
Views are read only objects used to fetch data. Views are created from a single or multiple table
objects. Views are like logical representation of data by presenting selected set of rows and columns by
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 174
joining different tables through data sources and relations between them. Frequently views represent a
subset of fields from a single table or multiple tables to simplify reporting.
A view consists of a query accessible as a virtual table to fetch a set of records from database
composed of the result set of a query. The data in a view is not stored as a database object but is
dynamically created when the view is accessed i.e. unlike ordinary tables in the database, a view does
not form part of the physical schema: it is a dynamically computed from data in the database. Changing
the data in a table alters the data shown in invocations of the view.
In Microsoft Dynamics AX, you can use views where tables are used. For example, you can use a
view in a form, a report, and in X++ code. The benefits of using a view instead of a table are as follows:
A view is normally used to retrieve and return only the data that is relevant for a
particular user or scenario.
Complex queries are used to create a view which will fetch and display customized set
of data i.e. a view represents data as a single table that is retrieved from single or
multiple joined tables that may use many conditions.
Performance can be improved using views by returning the relevant fields required by
user.
Better performance can be provided using view when compared with query as view
definition is compiled.
AX views are synchronized to the database. This makes views useful when you get a
requirement to read data from an AX table using external tools as you can fetch data
directly from the database instead of using the COM etc. interfaces.
Elements in Views
Metadata: This node is used to add Data Sources which forms query of the view.
Fields: This is used to add the required fields that will be displayed in the view.
Field Groups: These are very similar to tables field groups, which is a grouping of
logically related fields. I request you to review table field groups to get a complete
knowledge about the field groups.
Methods: We can write methods on views, which will reside in this node. The methods
in views resemble the table methods like insert() , update etc. and can be overridden.
We will see a sample method after creating the view.
With enough information on views, we will create a view as follows. The business requirement is
to display all the trucks and concerned zones in the views. Follow the below steps to create a view:
Right click on Views and click on New > View. Set the name of the view to
TruckZonesView from the properties page of view.
Expand Metadata node, right click on Data Sources and click on New Data Source. Set
the property Table of the newly created data source to ZonesTable. There is another
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 175
way to add data source. Just drag and drop the table onto the data source node of
metadata.
Expand the ZonesTable data source added in last step. You will see a data source node
in that. You can add one more data source which is used to create complex queries with
multiple tables. Add the TruckTable
into this Data Sources node. Set the
relations property of TruckTable to
Yes. This will get the relations from
table level into the view
automatically. This will reduce our
work of creating the relations on
views.
Now, expand the fields node in
ZonesTable and TruckTable data
sources created just now, drag and
drop the required fields onto the
fields node of view.
Save the view and open to see the
records that will be displayed.
Your view looks as in the adjacent
image
You can add the aggregate
functions to your view using Aggregate property of the views field.
Finally, Ranges node in data source is used to filter for a sub set of records, Group By
and Order By are used for grouping and sorting of records based on the given criteria.
Im leaving this to you for further practice. These will be however covered in Queries
topic. If you have any confusion on these, please return back after reading Queries
section to get a good idea on how queries work.
Methods
Methods on views can be used to display computed columns in the view. This will enable us to
do complex computations and display the result in the view. The following is an example method which
will display the count of trucks in same zone:
public static server str TotalTrucks()
{
str zoneId;
DictView dictView2;
dictView2 = new DictView(tableNum(TruckZonesView));
zoneId = dictView2.computedColumnString("TruckTable_1",
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 176
"ZoneId",
FieldNameGenerationMode::FieldList,
true);
return "Select count(TruckId) from TruckTable where TruckTable.ZoneId =" + zoneId;
}
The above method will create a SQL string that will bring the count of total available trucks in
the same zone with given criteria. To do this, we are using the computedColumnString() method. I leave
working and understanding of this method to you. Once the method is created, right click on the fields
node of view, click on New and create a computed column based on the type of value you return. Once
column is created, set the Name and View Method properties of the created field to appropriate values.
In this case, TotalTrucksInSameZone for Name and TotalTrucks to View Method. Plan for few other
methods to get good understanding on how computed columns work in views.
With enough work out on the views, we will move to next topic, Security Keys and Configuration
Keys.
License Codes
When AX is purchased, you will have to decide on system settings like number of users, number
of servers, access to MorphX and X++, the layers codes you need, and the features and application
modules list required to your company. Every system setting and module you will purchase, you receive
a license code. All license codes will be compiled in a code letter. These license codes are used for
controlling the features or parts of AX you will have access to. You can see and able to use only modules
with a valid license code in the main menu. Trying to access or execute an object without a valid license
code from AX will result in an error. Partners and customers will have different licenses.
You can also create new license codes when you develop your own verticals but need to
communicate with Microsoft to get license codes as they will generate license codes on behalf of you.
Usually, these license codes are required by Microsoft partners who will develop new verticals in ISV
layer or the partners who will develop feature packs in GLS layer.
Configuration Keys
Configuration keys disable features in the application for all users. Each key controls access to a
specific feature, and once it is disabled, the feature is automatically removed from the user view.
Configuration Keys determine what data should be visible to users.
To setup with only features needed for each particular installation, we define and use
Configuration keys. Administrators can reduce the potential surface attacks by disabling configuration
keys which help increasing the security of their AX installation. Configuration keys are arranged based on
functionalities which can be used to enable or disable particular functionality in the application.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 177
The configuration keys are listed in the Application Object Tree (AOT) under Data
Dictionary>Configuration Keys. Most of the AOT elements can be controlled by associating a
configuration key to that element. A property called ConfigurationKey is available for elements those are
set with particular configuration key have a property that is named. If the property value is empty, the
element is not controlled by a configuration key.
A large number of AOT element types can be controlled by configuration keys in which some of
them are as follows:
Extended data types
Fields
Form controls
Menu items
Menus
Report controls
Tables
To create a configuration key, follow the below steps:
Right click on Configuration Keys in AOT. Click on New > Configuration Key.
In the properties window of newly created configuration key, set the properties Name
to Zones and Label to Zones configuration key.
Save the changes.
Now, let us set this configuration key to the table ZonesTable. To do this, follow the below
steps:
Open properties of the table ZonesTable.
Set the property ConfigurationKey to Zones.
Save the changes.
To enable or disable a configuration key, go to System Administration module. Under Setup
section, in Licensing group, you will find a menu item called License Configuration. Click on License
Configuration menu item to open the following form:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 178
In the above form, as you can see the configuration key just created, either enable or disable
using the check box provided and click on Apply. This may ask you to synchronize. Synchronization will
update the objects based on status of the configuration key you updated. It is recommended to
synchronize and click on Ok.
You can also enable or disable the configuration keys using the X++ program as follows:
static void SwitchConfigKey (Args _args)
{
ConfigurationKeySet configKey = new ConfigurationKeySet ();
str msg;
configKey.loadSystemSetup ();
//The below call will check if the //configuration key is enabled or not.
if (isConfigurationKeyEnabled (configurationkeynum (Zones)))
{
// The below call will enable/disable the configuration key based on the boolean value we
// pass. In this case, it will disable the configuration key.
configKey.enabled (configurationkeynum (Zones), false);
msg = "Config Key disabled.";
}
else
{
configKey.enabled (configurationkeynum (Zones), true);
msg = "Config Key is enabled.";
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 179
SysDictConfigurationKey::save (configKey.pack ());
//The below call will reload and refresh security and synchronize your AOT.
SysSecurity::reload (true);
info (msg);
}
Note: It is recommended to use the existing configuration keys related to each module and
create very few if required for each module.
Finally, lets understand what happens if we disable configuration key for a table or column:
AX 2009 and
previous versions
In earlier version like Microsoft Dynamics AX 2009 and others, the column or table
for which configuration key is disabled, is dropped from physical database. This
used to break cubes and other functionality, which is responsible to result in
unexpected results at runtime. In addition to this, if there is any external
application that reads data from physical database directly like an SSRS report
generated directly, are broken and may not run even in certain cases.
When you write an X++ SQL query, this does not return the disabled column and
will not return any error.
AX 2012 When a field is deleted, it is not deleted at physical database level. i.e. the
underlying column is not dropped when a field is disabled instead, you will not get
any result when you try to execute X++ SQL statements i.e. a query that selects a
disabled field does not receive that field in the results set.
Regarding data, administrators who disable a table or field can decide whether
the data should be deleted or not deleted from underlying database table or
column. This will not reflect on cubes etc., which is an advantage.
A table is made a temporary table when you disable configuration key of the
table. For more information about temporary tables, please refer tables section.
For other stuff like unique index etc. developer or administrator should take care
before they disable the configuration keys on them to make sure they may not
violate the business rules.
With enough information on configuration keys, lets consider a scenario. If you like
enable/disable an entire feature or a feature to all the users you enable/disable configuration keys. How
about if you like to enable or disable the feature for particular group of users? The answer is Security
Keys.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 180
Security Keys
Security keys set access of an object to a user or a group of users. This will enable or disable a
feature or access of object to that group of users. Setting the accessibility to a group of users is easier
than for each user due to a fact that the work will increase. If the users are related to same
department/job, it is better to have a group for them and associate the permissions they need to access
objects. This kind of security is achieved through security keys. Security keys will have property
Configuration key, which can be controlled by using this.
Security keys should be set to the objects like tables, maps views, menu items etc. Missing of
security key to the object will make that available to all users on which you will lose control on that
object. Security keys also allow you to set access levels. Access levels include no access, view, edit,
delete. These access levels can be set at data dictionary and the menu items. We use the property
MaxAccessMode to define the access level for a user at tables, maps and views you use. By default, you
set MaxAccessMode property with value Delete to Tables which will allow the users full access to a
table. You can change this access level as per business requirement and the user usage requirement. In
contrast to tables Menu Items will have the property NeedAccessLevel which can be used to set the
access levels. This can be used to specify the required access level to execute the menu item. The
default level for this is View and should be considered before changing this which may lead to unwanted
results.
You can create security keys in following way:
1. Right-click the Security Keys node, and then select New Security Key that is available under Data
Dictionary node.
2. Right-click the newly created security key and click Properties.
3. Rename the security key accordingly by modifying Name property.
To apply security key to the other objects [i.e. form control, table etc.] , right click the object,
click on properties and in the properties of the object, select the security key you like to assign and click
on save. Once you are set, you can use this to enable or disable who can access the object and set
control user level accessing discussed as below.
Permissions determine accessing of menus, forms, reports, and tables at user level or user group
level. Permissions in Microsoft Dynamics AX are assigned to user groups instead of individual users as it
will be more complex and time consuming to assign permissions to each user where most of the cases,
group of users will do particular operation and assigning permissions to groups saves time because you
do not have to adjust permissions for each user.
You can create user group from Administration vertical which is not set to No access for all
security keys [i.e. No access to all menus, forms, reports and tables etc.]. So, whenever you create a new
group, you should follow this procedure to give accessing to the users in the group accordingly as per
their roles.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 181
Security keys are setup from Administration > Setup > Security > User group permissions in the
Permissions tab. You can define access to menu items, form controls, tables and fields within the
security profile. Permissions are granted using the following access levels, which is discussed in following
table:
No Access This will restrict the users of the group to that item and sub-items it controls.
View
Users of this user group can view the item and no other commands like Save etc.
can be used.
Edit
Users of this group will have view, edit and update permissions. New and
Rename cannot be done.
Create Except delete, all other actions can be done by the users of this group.
Full control
Users of this group will have full control and can perform any action and no
commands are disabled.
Following are notable points It is recommended to define security access for each user before
first login as a best security practice to avoid misuse of permissions by the users.
Security keys are obsolete in Microsoft Dynamics AX 2012 and only exist to use for reference
during a code upgrade. There is a new security framework, which is called role-based security. For time
being, you can assign the roles to users from System Administration > Setup > Security > Assign users to
roles, where a role called as security role describes what a user can do and what not using the duties
and privileges.
With a good understanding on security keys and the updates in AX 2012, we will move to
advanced topic, table collections.
Table Collections
A table collection is a set of tables that will not have foreign key relationships with tables
outside the table collection. Each table in a table collection will be there only once but a table can be in
multiple table collections as per business requirement. Data is not stored in table collection. Only
companies and virtual companies store data.
Deciding what tables should be included into a table collection requires some investigation
which includes the following best practices to be covered to get best expected results:
A table which has foreign key relationship with other tables is recommended not to
include into table collection. If it is a mandatory requirement, it is recommended to
include the referenced table also. Referential integrity can be affected if business logic
that accesses the shared table does not have access to records in the referenced table.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 182
All the tables that have composite relationship should be part of the table collection,
which will avoid further complications.
Adding tables like Countries, States etc. which are not specific will not have any effect as
the records will be available to all organizations and will make sense.
Creation of table collections is a simple task which includes drag-and-drop operations in
Application Object Tree as follows:
Expand Data Dictionary node of AOT.
Right click on Table Collections and click on New Table Collection.
Open the properties pane of newly created table collection, give a good name to the
newly created table collection accordingly and save.
Now, drag and drop the tables you need into this collection. It is recommended to open
another instance of AOT to drag and drop the tables which will make the work speed.
There are few table collections already provided which may be used and can be updated adding
new tables into the collections as per business requirement. Lets see the table collections in action
applying them to virtual companies discussed in the following section.
Virtual Companies
Data in AX tables may be defined to store company specific and shared records, across the
companies. This depends on requirement of the business. Let us consider a scenario where the data like
customers should be stored across different companies in organization. There are 2 ways to do this, one
duplicate data every time when we create a new record into all companies or second will be, share the
data across the group of companies you need. This sharing may be done across all the companies or only
to a required group of companies. Sharing of data depends on the type of data you like to share across
the companies. This may include master data, transactional data, reference data etc. Sharing of data
depends purely on organizational policies and requirement.
By default, users can access only data of particular company they are logged into. When you
share the data across companies, they can access the data from different companies based on
configuration. This is done using the virtual companies where you specify the companies that should
share and the data that should be shared.
Virtual companies are used to share the tables and their data among group of companies. Let us
consider a scenario where you like to share a table called accounts which are common to a particular
group of companies. In this case, you cannot make the data free of all companies i.e. share to all
companies as this is related to a particular group of companies. In this case, you create a virtual
company which will share the data to the companies you configure while creating the virtual company.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 183
In a virtual company, when users save information in one of the tables of the table collection,
the data is available to the other company accounts in the group configured with virtual company.
Please note that a company is a legal entity and company is the only kind of legal entity that you can
create, and every legal entity is associated with a company ID. When you select a physical company or
legal entity that is configured with a virtual company, the dataAreaId of virtual company is stored
instead of physical company in tables when you perform CRUD operations.
Set up of virtual companies is a preprocessing task i.e. it is recommended to set up virtual
companies when you first implement AX and before data is entered into tables. There is a high chance of
affecting data integrity if the data has already been there in tables as the table combines records into a
shared table at later time when you do this. Most probably virtual companies are used to share master
data and reference data. It is not recommended to share transactional data which will be company
specific.
To create and test the working of virtual companies it is recommended to have 3-4 companies
where you can have different exercises that will make you completely understand the use of virtual
companies in practice. By default, you will get one company DAT. You can create 3 more companies
namely 01, 02 and 03 as follows:
Go to Organization Administration vertical.
In Setup find Organization
Click on Legal Entities. You will see a form getting opened.
Enter the values as per the requirement and click on OK. Set the properties like
Language, Region as per local/regional settings and save the record.
Repeat the above steps for all the companies that should be created.
Now that you are set with multiple companies namely, DAT, 01, 02, 03. The following sample
shows you how to create and configure virtual companies in practice:
Create a table collection. Name it SharedBankAccounts. Drag and drop BankAccounts
table or some other table created in exercises in previous sections of this book.
Go to System Administration Vertical. Find Virtual company accounts in Setup group and
click. This will open a form which will list the available virtual companies in the current
instance.
Click on New, in the grid below, type VIR under Company accounts column and Bank
virtual company accounts. under Name of company accounts column.
Click on save. Now you will find 2 more tabs namely Company accounts and Table
collections.
Click the Company accounts tab, and then select the company accounts to include in
the virtual company. In this case, select 01 and 02. Click OK if you get any warning
message boxes.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 184
o To add a company account, select the company name in the Remaining
company accounts list, and then click the left arrow button (<) to move the
company account to the Selected company accounts list.
o To remove a company account, select the company name in the Selected
company accounts list, and then click the right arrow button (>) to move the
company account to the Remaining company accounts list.
Now, in the Table collections tab, select the table collections you need to share data
into the virtual company. This will share the data in the tables that are referenced in the
table collections you selected. In this case, select SharedBankAccounts, which is created
above. Click on OK if you get any warning message boxes. Once you configured as
above, your form should look like this as in the following image:
Create a virtual company
Select the company accounts to include into the
virtual company
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 185
Select the table collections to share in the virtual
company
Now, you are ready to test the working of virtual company you created. Save the
configuration. You may need to close and open the client once. Usually AX environment
will close the session forcibly. It is recommended to do this to reflect the changes.
Note: When you create a
virtual company, it is not
shown in the list of
companies but is managed
by AX environment as can
be seem in the adjacent
image.
Now, its time to test the working of virtual company. To do this, create couple of records in
company 01, 02 and 03 respectively using table browser and find the difference. The difference is shown
in the following images:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 186
If you observe the above images, for companies 01 and 02, dataAreaId taken is vir, which is a
virtual company id but the title of the table browser is showing actual company ids 01 and 02. For the
company 03, dataAreaId taken is 03 as this is not included in virtual company. The data of companies 01
and 02 are shared and can be viewed in both the companies. This is the reason why it is recommended
to configure virtual companies before inserting data into tables. There are few points to note before
creating and configuring virtual companies as follows:
Except the Application Object Server (AOS) instance connected by administrator, all other
instances must be shut down.
Better to allow only one active client, connected by administrator as you may get unwanted
results if multiple sessions are opened and possibly, everyone should close and open the client
for changes to take effect.
The tables used by virtual company should not have data. At least, the tables should not contain
the data specific to companies that are included in virtual company. The existing data is not
moved to the virtual company. Therefore, data can be corrupted, and you may have to manually
update records in the database. i.e. creating virtual companies and adding tables to the virtual
companies is a pre-processing task.
Though it is not recommended, you can also use non-administrators to create and configure
virtual company accounts.
When you delete a virtual company, the shared data that is associated with the virtual company
is not deleted automatically. This data remains available in crosscompany queries. To delete a virtual
company, you must remove the associated data from the tables that were shared via the virtual
company.
With enough information and knowledge on virtual companies, we will move to the next topic,
Macros.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 187
Macros
A macro is a precompiled variable or statement that is replaced wherever it is called. X++
includes facility to write macros and also there are a large number of pre-defined macros available in AX
which can be used based on requirement. Macros may not be commonly used but there are a large
number of advantages few times using macros instead of standard X++ coding. Macros are mostly used
to define constants, the selection of fields in a query, which can be reused in multiple places, Keep track
of the list of fields stored using dialogs etc. are some of the cases where we use macros.
Macros can be created at 3 places as follows:
Method level i.e. in the method.
Class level i.e. in the declaration of class, where these can be used throughout the class
in any method.
AOT level i.e. in Macros node of AOT, which can be used in any object/method you
create throughout the AOT.
We will see all the types of declarations and use of each type. The following example shows how
to declare and use a macro at method level. Note that we are using the #define construct to define
macro:
static void SimpleMacro(Args _args)
{
#define.LastWish ("Have a great day.")
info (#LastWish);
}
The above program will display Have a great day. as output in the info box. We will try to
understand what we are doing in the above program:
A macro is declared using #define statement with a name assigned to macro,
(LastWish in the example above) with a period in between both. The #define directive
tells the pre-compiler to create the macro variable, including an optional value.
The macro is assigned a value in between ( and ). Here, we assigned a string constant.
We can assign the values of any type. The variable can have a value that is a sequence of
characters, but it is not required to have a value
To access the macro, simply used the macro name with# as prefix to indicate that its a
macro. The value of a macro variable can be written into the X++ code at any location,
by giving the name of the macro with the # character added as a prefix.
When you use in this way, this will substitute the value assigned to macro in the place
where the macro is used. In this case, the call #LastWish is replaced with Have a great
day., which is displayed as output. All pre-compiler directives and symbols begin with
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 188
the # character. All of the directives and symbols are handled and removed before the
resulting X++ code is given to the X++ compiler.
You can also check whether the macro is defined or not using #if. In the following
example, you can observe the usage of #if where we are trying to find whether a macro
called LastWish is defined using the statement #if.LastWish. If the macro is defined, it
will enter into the next step and execute all the statements that are declared in
between #if and #endif. Please note that every #if should be ended with #endif. If the
macro is not defined, it will not execute the statements in it. In the following example as
the macro is defined, we get an output which demonstrates that as below:
static void SimpleMacro(Args _args)
{
#define.LastWish("Have a great day.")
#if.LastWish
info(#LastWish);
#endif
}
You can even un define a defined macro using #undef. In the following example, we are
un defining a macro using #undef statements like, #undef.LastWish and trying to check
the existence of the macro using #if. The next statement will not give error because,
that will be taken care only if macro exists. But, the last line, the info() will give an error
as the macro is undefined and we are trying to access something which is not at all
existing. The below example demonstrates everything as follows:
static void SimpleMacro(Args _args)
{
#define.LastWish("Have a great day.")
#if.LastWish
info(#LastWish);
#endif
#undef.LastWish
#if.LastWish
info(#LastWish);
#endif
//info(#LastWish); //Will result in error as the macro is undefined already
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 189
Using a macro as a constant rather than entering the value in code makes it easier to
maintain. This declared constant can be used in multiple places and while if you need to
modify the value, you can modify at only one place instead of modifying multiple places.
We can define a macro using #localmacro directive. The #localmacro directive can be
used when you want a macro to have a value that is several lines long, or when your
macro value contains a closing parenthesis. The #localmacro directive is a good choice
when you want your macro value to be lines of X++ or SQL code. In the below example a
macro is declared using #localmacro and is ended using #endmacro. Note that a
#localmacro should be ended with #endmacro. We can also use #macro directive
instead of using #localmacro directive but the recommended is, #localmacro. The below
example declares fields list of X++ SQL, which is used in method to select the fields in
X++ SQL query instead of manually listing all the fields in the select statement. The
following example demonstrates this:
class MacroSample
{
#localmacro.Fields
AccountNum,
AmountAvailable
#endmacro
}
public static void main(Args args)
{
BankAccounts bankAccounts;
while select #Fields from bankAccounts
{
info(strFmt("AccountNum: %1 - Balance : %2",
bankAccounts.AccountNum, bankAccounts.AmountAvailable));
}
}
In the above example, #Fields is used instead of hard coding all the fields and you can
use this #Fields in any number of methods in the class. For this kind of multiline
declarations, localmacro can be used. Please note that #if and #undef doesnt affect
#localmacro. To discard a #localmacro, we can redefine a macro using #define which will
override the #localmacro defined one. You can also use #globalmacro to get the same
use as given by #localmacro but is not recommended due to a fact that overriding is not
guaranteed with #globalmacro over #localmacro.
You can use #linenumber to get the current line number the cursor in. This can be a
useful tool while debugging but is not recommended though. The example is as follows,
which will return 11 as line number:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 190
public static void main(Args args)
{
BankAccounts bankAccounts;
while select #Fields from bankAccounts
{
info(strFmt("AccountNum: %1 - Balance : %2",
bankAccounts.AccountNum, bankAccounts.AmountAvailable));
}
info(strFmt("%1", #linenumber));
}
The following example shows how to do nesting of macros in X++. In this, a macro is
used inside another macro and until all the macros are declared, you cannot use the
macro and if you try, you will receive an error:
static void NestedMacros(Args _args)
{
#define.Area(#PI + "*r*r")
// The next line of code would return an error message "The macro does not exist. ",
// as #PI in the value of #Area cannot be expanded before it is defined.
//info(#Area);
#define.PI("3.142")
info(#Area);
}
You can also pass values to macros. The values passed are accessed using the
convention similar to strFmt(), like %1, %2 and so on. It is a better practice to check if
the value is given or not before doing operation on values assuming you are given the
values always. To verify whether the values are passed are not, you can use #if.empty()
and #ifnot.empty(). Both of them are ended with #endif. #if.empty() will verify if the
argument is passed or not and enters to execute statements in it if the argument is not
passed i.e. empty. Whereas, #ifnot.empty() works in the reverse. The following example
shows the working of passing values as well as using #if.empty() and #ifnot.empty():
static void PassValuesToMacros(Args _args)
{
real result;
#localmacro.Area
#ifnot.empty(%1)
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 191
result = 3.142 * %1 * %1;
#endif
#if.empty(%1)
info("Empty value given.");
result = 0;
#endif
#endmacro
#Area(7)
info(strFmt("Area = %1", result));
#Area()
info(strFmt("Area = %1", result));
}
The output of above program looks as follows. I leave the understanding of output to you :
In addition to the above said methods, you can also add macros to AOT, which can be
used throughout the AX environment wherever you write the code. This can be used if
the macros may be used in many places or used for declaring and using constants etc.
Standard AX 2012 provides a handful of macros by default under Macros node of AOT.
These are usually called as Macro Library. You can access the macros of macro library as
any other macro you use. The following example uses one of the macros declared in
AOT Macros, namely smmDateMacros:
static void UseMacroLib(Args _args)
{
#macrolib.smmDateMacros
info(strFmt("%1", #January));
}
If you notice in the above example, #macrolib is used to reference smmDateMacros in
your program. It is recommended to use this though not mandatory to identify the
usage of AOT macros uniquely. You can also refer this in your program using
#smmDateMacros directly and you dont get any error, instead, it works same until the
macro name is not overridden in local context using #define or #localmacro.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 192
Creating an AOT macro is also a very easy task. To create an AOT macro, right click on
Macros node in AOT, click on New > Macro. Name it DaysOfWeek and add the following
lines to that. This is just to simulate 3 characters notation of weekdays:
#define.Sun('Sunday')
#define.Mon('Monday')
#define.Tue('Tuesday')
#define.Wed('Wednesday')
#define.Thu('Thursday')
#define.Fri('Friday')
#define.Sat('Saturday')
Now, write the following program to test the working of newly created macro library,
you should see an infolog with Wednesday displayed in it:
static void CustomAOTMacro(Args _args)
{
#DaysOfWeek
info(#Wed);
}
From all the above types of macros, except AOT macros, every macro has got its own
scope and once they go out of scope, they cannot be referenced/used. If you try to do
so , youll end up with errors.
As macros are pre compiled, it is typical to identify the errors and fixing the errors in
macros is different than regular X++ code. Macros may cause errors at 2 places. One is,
while macro compilation, which is lexical issue and is generated at pre compile time. The
example for such kind of error is, #define.Lexical(info("Hello");), which confuses the
compiler with the characters ;). Second will be a syntax error like #define.Syntax(%1
** %2), which will compile properly but will generate an error when you try to use the
macro like #Syntax. This is due to a fact that there is no operator available like ** which
will cause error.
You can also define a macro in another macro. This is not nesting of macros instead,
defining an inner macro in an outer macro as follows:
#localmacro.Outer
#define.Inner(5)
#endmacro
While using macros that are declared in class declaration in inheritance, macros are also
inherited and can be used in child classes that are declared in parent classes.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 193
Macro names declared and used in X++ are case-insensitive. It is not mandatory that
they should be used only in the case they declared. But it is recommended to declare
and use the macros in capital case as a best practice to identify them.
You can also check if a macro value is equal or not before proceeding to next step as in
the following example:
static void TestMacroValue(Args _args)
{
#define.PiMacro(3.142)
#if.PiMacro(3.142)
info("Pi: " + num2str(#PiMacro, 4, 3, 1, 0));
#endif
#define.PiMacro(4.142)
#ifnot.PiMacro(3.142)
info("Wrong Pi value: " + num2str(#PiMacro, 4, 3, 1, 0));
#endif
}
The program is fairly straight away, verifying for the value whether equals or not equals.
If equals, display one output and not equals, displaying another output. The program
will give you the following output as you are overriding the value of Pi with a new value,
4.312 which will make the second condition true:
Pi: 3.142
Pi value: 4.142
With a fair amount of enough understanding on macros, its time to move to much more
important topic, Queries. Though queries and CRUD operations are covered in earlier topics, a fair
amount of discussion is required to handle more complex scenarios which will be discussed in this
section.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 194
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 195
Queries
Let us consider a scenario where you need to retrieve data from tables in your X++ code which is
used for doing various operations which may include extracting relevant information from database
table or join multiple tables and finding relevant records like invoices of customers etc. to do these kind
of operations, we use queries.
Queries allow the user to extract the relevant information from database tables. When you
need a particular record from database table, you can use queries. You can also extract all the records in
a table using the queries but instead of scanning complete table, you can filter for the values based on
the columns values and extract the information you need using the queries. Queries are the only way to
extract tables records and this is the very basic advantage. Using queries, you can reduce the amount of
coding required to perform some complex tasks, especially if these tasks involve multiple tables. You can
also select only the fields you need, use few keywords which can be used for sorting and to do some
aggregate operations like sum, average etc.
Microsoft Dynamics AX 2012 accesses data using select statements and queries. AX has got
different ways to create queries as follows:
Inline queries, which are used in X++ while writing code and are frequently called as X++
SQL statements. These are written in code directly for a particular scenario.
AOT Queries also called as static queries, which can be used anywhere throughout AX in
X++ code, views, reports and other places according to the requirement. We have a
query object model in AX, which includes various classes which are used while using
these queries.
X++ Queries are also called as dynamics queries, which will simulate AOT queries and
are developed using X++ code. These also use query object model of AX for creating and
using the queries.
You will see each of the types with a good number of examples. I request you to be clear with
this topic and go through this topic until you are very clear on using the queries in AX.
Inline Queries
Inline queries are the simple X++ select statements that are written in methods to fetch the data
from database tables. A brief introduction with a fair amount of examples is given in earlier sections
about using these. This section will focus on more complex operations that can be done using the select
statements. Syntax of select query is as follows:
select <parameters>
where, parameters may be one or more of these:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 196
[ [ FindOptions][ FieldList from ] ]TableBufferVariable[IndexClause][ Options][ WhereClause][
JoinClause]
To use the queries, you first create a table buffer. A table buffer is a variable of the table type.
The following example shows this:
TruckTable truckTable;
truckTable is called as table buffer of type TruckTable. You can create references to tables
similar to classes in AX. This is called as table buffer. Table buffer will have the record that is selected
and can be used to select the records sequentially. The following examples will use the table buffers for
fetching records.
This section uses the tables ZonesTable and TruckTable to demonstrate using of queries. All the
queries can be executed from anywhere i.e. class methods or table methods or in a job etc. The
following table describes each option with a sample X++ query:
Using where clause
Selecting a specific record. You can observe that a
where clause is used for selecting a particular
record. Here, the record is filtered using a string
value and enum value. You can also filter using any
type like integer, real etc.
static void InlineQueries(Args _args)
{
TruckTable truckTable;
//Select a specific record
select truckTable where truckTable.TruckId ==
"T0003";
info(strFmt("%1, %2", truckTable.TruckId,
truckTable.Description));
//Select a specific record
select truckTable where truckTable.TruckType ==
TruckType::Small;
info(strFmt("%1, %2", truckTable.TruckId,
truckTable.Description));
}
Using while select
Selecting all the records one by one with specific
fields i.e. field list.
We use while select to select the records one by
one. The field list is given to select only particular
fields which will increase performance of query
when you dont need all the fields. Note that the
static void SelectionDemo(Args _args)
{
TruckTable truckTable;
//Select all records one by one from the table
while select TruckId, Description from
truckTable
{
info(strFmt("%1, %2", truckTable.TruckId,
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 197
fields which you are not selecting will return
empty values if you try to use them in your
program.
truckTable.Description));
}
}
Using aggregate functions
Selecting using aggregate functions. You have the
following aggregate functions:
avg Used to find average
count Used to find count
sum Used to find sum
maxof Used to find maximum
minof Used to find minimum
In the example you can observe usage of count()
and maxOf(). Note that, no other fields are
selected as when you do aggregation, it will be
done for multiple records and you cannot fetch a
particular field. You can use GroupBy clause in few
cases which will be discussed below.
static void InlineQueries(Args _args)
{
TruckTable truckTable;
//Use aggregate function count()
select count(TruckId) from truckTable;
info(strFmt("%1", truckTable.TruckId));
//Use aggregate function maxOf()
select TruckId, maxOf(TruckDimensions) from
truckTable;
info(strFmt("%1",
truckTable.TruckDimensions[4]));
}
Using order by clause
Order by clause is used to sort the records in
ascending or descending order. You give a field
name using which the records are sorted in order
and retrieved from database table. The example
shows sorting TruckTable records in ascending and
descending orders in the order of TruckId.
static void InlineQueries(Args _args)
{
TruckTable truckTable;
//Select all records in a table in ascending sorting
order
while select TruckId, Description, TruckType from
truckTable order by TruckId asc
{
info(strFmt("%1, %2, %3", truckTable.TruckId,
truckTable.Description, truckTable.TruckType));
}
//Select all records in a table in descending sorting
order
while select TruckId, Description, TruckType from
truckTable order by TruckId desc
{
info(strFmt("%1, %2, %3", truckTable.TruckId,
truckTable.Description, truckTable.TruckType));
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 198
}
Using group by clause
The example uses the group by clause with a field.
Note that most probably grouping is used only on
the fields which will be used multiple times in a
column like the account type or customer type etc.
Usually, an enum represents this. You can also use
grouping on fields like customer id in transactions
table where you can find the sum of amount or the
count of transactions done by the customer.
Group by is mostly used in combination with
aggregate functions.
static void InlineQueries(Args _args)
{
TruckTable truckTable;
//Select all records in a table with grouping options
while select sum(TruckDimensions), TruckType
from truckTable group by truckTable.TruckType
{
info(strFmt("%1, %2",
truckTable.TruckDimensions[4],
truckTable.TruckType));
}
//Select records in a table with grouping options
using aggregate functions
while select count(TruckId), TruckType from
truckTable group by truckTable.TruckType
{
info(strFmt("%1, %2", truckTable.TruckId,
truckTable.TruckType));
}
}
Selecting a value from EDT array
You can select a value from EDT array using the
index of the array as shown in the example.
static void InlineQueries(Args _args)
{
TruckTable truckTable;
//Get a value from EDT array used as field in table
while select TruckId, Description, TruckDimensions
from truckTable order by TruckId desc
{
info(strFmt("%1, %2, %3", truckTable.TruckId,
truckTable.Description,
truckTable.TruckDimensions[3]));
}
}
Using reverse keyword
The reverse keyword is used to fetch the records in
reverse order as in the given example. This works
more or less equivalent to sorting in descending
static void InlineQueries(Args _args)
{
TruckTable truckTable;
//Selection of records in reverse order
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 199
order. while select reverse truckTable order by TruckId
{
info(strFmt("%1, %2, %3", truckTable.TruckId,
truckTable.Description,
truckTable.TruckDimensions[3]));
}
}
Using firstFast and firstOnly
firstFast will select and return the first record
faster comparatively. In few situations, you may
need to display first record and may traverse later
based on requirement like in dialogs. This is
achieved using firstFast keyword.
firstOnly is used to select only the first available
record. Though a simple select will return only one
record, few places like finding the first related
record or checking for existence of record in
related table needs firstOnly keyword. Note that,
firstOnly may not be used in while select
statement where you may need to select more
than one record.
In addition to firstOnly, you have few more
statements,
firstOnly10, which is same as firstOnly except that
this returns 10 rows instead of one.
firstOnly100, which is same as firstOnly except that
this returns 100 rows instead of one.
firstOnly1000, which is same as firstOnly except
that this returns 1000 rows instead of one.
//Using firstFast
static TruckTable find(TruckId _truckId,
boolean _forUpdate = false)
{
TruckTable truckTable;
;
if (_truckId)
{
select firstFast truckTable
where truckTable.TruckId == _truckId;
if (_forUpdate)
truckTable.selectForUpdate(_forUpdate);
}
return truckTable;
}
//Using firstOnly
public static boolean exist(TruckId _truckId)
{
boolean found;
;
found = (select firstOnly
RecId
from
truckTable
where
truckTable.TruckId == _truckId).RecId !=
0;
return found;
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 200
Using index and index hint
Index keyword is used to instruct the database to
sort the selected records as defined by the index
whereas index hint gives the database a hint to
use this index to sort the selected records as
defined by the index. The database may ignore the
hint.
Please note that improper use of index hint will
cause serious performance issues. Index hint
should be applied only to the SQL statements that
do not have dynamic where clauses and/or order
by clauses.
static void InlineQueries(Args _args)
{
TruckTable truckTable;
//Selection of records using index
while select TruckId, Description, TruckDimensions
from TruckTable index TruckIdx
{
info(strFmt("%1, %2, %3", truckTable.TruckId,
truckTable.Description,
truckTable.TruckDimensions[3]));
}
//Selection of records using index hint
while select TruckId, Description, TruckDimensions
from TruckTable index hint ZonesTableIdx
{
info(strFmt("%1, %2, %3", truckTable.TruckId,
truckTable.Description,
truckTable.TruckDimensions[3]));
}
}
Using noFetch
This will not fetch any record into the table buffer
and you see empty output in the infolog. You can
use this to pass the table buffer to a method which
will do actual fetch operation on database table.
Indicates that no records are to be fetched at
present. This is typically used when the result of
the select is passed on to another application
object, for example, a query that performs the
actual fetch.
static void InlineQueries(Args _args)
{
TruckTable truckTable;
//Selection of records using index
select nofetch TruckId, Description,
TruckDimensions from TruckTable;
info(strFmt("%1, %2, %3", truckTable.TruckId,
truckTable.Description,
truckTable.TruckDimensions[3]));
}
Using forUpdate
forUpdate is used to select the records exclusively
for update. When you need to update a value or
values of a selected record, you should select the
static void UpdateRecords(Args _args)
{
TruckTable truckTable;
//Select a record using forUpdate to update record
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 201
record for update. It is mandatory to select the
record for update if you like to update the record
and If you dont select for update, you will get
errors. For more information, see transaction
tracking system section on updating of records
and for update.
ttsBegin;
select forUpdate truckTable where
truckTable.TruckId == "T0005";
truckTable.TruckType = TruckType::Large;
truckTable.update();
ttsCommit;
}
Using concurrency controls
You can also specify the concurrency models to
use while selecting records from a table. Microsoft
Dynamics AX supports the following concurrency
control techniques:
optimisticLock: Optimistic Concurrency Control
helps increase database performance. Optimistic
Concurrency locks records from the time when the
actual update is performed.
pessimisticLock: Pessimistic Concurrency Control
locks records as soon as they are fetched from the
database for an update. Pessimistic concurrency
was the only option available in previous versions
of Microsoft Dynamics AX. In the current version,
optimistic or pessimistic both can be used based
on the requirement.
static void InlineQueries(Args _args)
{
TruckTable truckTable;
//Select a record using optimistic concurrency
model
select optimisticLock truckTable where
truckTable.TruckId == "T0005";
info(strFmt("%1, %2, %3", truckTable.TruckId,
truckTable.Description,
truckTable.TruckDimensions[3]));
//Select a record using pessimistic concurrency
model
select pessimisticLock truckTable where
truckTable.TruckId == "T0005";
info(strFmt("%1, %2, %3", truckTable.TruckId,
truckTable.Description,
truckTable.TruckDimensions[3]));
}
In addition to the above said, we have few other
keywords namely,
forceLiterals
forceNestedLoop
forcePlaceholders
forceSelectOrder
Each keyword has its own purpose where they are
used to change the select order in using joins, to
force using nested loops and instruct the database
to reveal actual values which are used in certain
situations to improve performance as well as get
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 202
the desired output.
With a complete overview of simple select statements, there will be always advanced
requirement exists which will be there while programming using select statements. The requirements
may be one of the following:
Select records from multiple companies at a time
Select the records from more than one table at a time.
The first case requires selection from cross companies whereas second one requires joins. We
will see both of them in following text.
Using crossCompany
crossCompany is simply used to fetch data from all or selected companies that the user is
authorized to read from. To read from specific companies, a container is added to reduce the number of
companies involved. The following example demonstrates how to retrieve the records from all the
companies and from specific companies:
static void CrossCompanyExample(Args _args)
{
BankAccounts bankAccounts;
container conCompanies = ['01','03'];
;
while select crossCompany * from bankAccounts
{
info(strFmt("%1, %2", bankAccounts.AccountNum, bankAccounts.Name));
}
while select crossCompany : conCompanies * from bankAccounts
{
info(strFmt("%1, %2", bankAccounts.AccountNum, bankAccounts.Name));
}
}
In the above example, first while select will fetch records from all the companies whereas
second one will fetch records only from companies 01 and 02 as we are passing the companies list as
container to select the records from. In this way we can perform multi companies selection of records
from database tables using X++.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 203
Database Joins
Database joins are used to join tables on a column that is common to both tables. When you
join the tables, you fetch records from both the tables. To understand joins better, let us consider a
scenario where you need to process 1000 records and like to update records in related table. You can do
this using two ways, one of them is using a nested loop, which will take 1001 round trips to the database
using a while select and the second option is to use join which will take only a single trip to database.
Joins reduces the number of SQL statements that are needed if you want to loop through a table and
update transactions in a related table. The basic advantages of using joins are as follows:
Performance is increased if you use joins. CRUD operations can be done on multiple
tables.
Navigation can be done in both the tables. When user navigates in one table, the related
table is updated based on the relation between the tables.
Joins linked to tables return values together through which the number of round trips to
database gets reduced.
X++ supports 4 types of joins in X++ SQL statements. Each of the type will fetch records on tables
based on the criteria that match. You have new keywords to use joins. Not only there is additional
syntax needed to specify what table is being joined, but also the where clause has to be modified.
Within the where clause, the two fields that relate the tables must be specified in addition to any other
conditions of the search.
Before going into examples and discuss more about the types of joins, lets have some data in
the tables to understand how the joins work. In the previous exercises you created, 2 tables were taken
namely, ZonesTable and TruckTable with the following records:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 204
The following table discusses the types of joins available in X++ with examples and the output of
different joins:
Inner Join: Following is the behavior of
inner join:
2 or more tables are joined with inner
join using the join keyword. You dont
have any keyword inner to use with
inner join.
Inner join combine records into one
table only when there are matching
values in a common field, otherwise,
the record is skipped.
The records are retrieved from all the
tables joined. In the example given, the
records are populated from ZonesTable
and TruckTable also.
If there are more than one related
records in child table, the parent
records are populated multiple times
which will be equivalent to the count of
the related records in child table.
You have to add a relation using the
where clause while using inner join.
You can also add multiple filters in
where clause in addition to the existing
one.
Please observe the output of the
program to understand how the inner
join will return the results.
In the output, you can see the results
extracted from two tables joined, and
the second one using another filter.
Note that the record of ZonesTable is
taken multiple times as there are 2
matching records in the TruckTable.
This join is mostly used in cases where
you need to fetch the records from all
the tables that are joined using a
related field and you need to traverse
through all the records of both tables
static void InnerJoinExample(Args _args)
{
ZonesTable zonesTable;
TruckTable truckTable;
info("With no extra filters in where clause");
while select zonesTable join truckTable where
zonesTable.ZoneId == truckTable.ZoneId
{
info(strFmt("%1, %2, %3, %4", zonesTable.ZoneId,
zonesTable.ZoneType, truckTable.TruckId,
truckTable.Description));
}
info("With an extra filter in where clause");
while select zonesTable join truckTable where
zonesTable.ZoneId == truckTable.ZoneId &&
zonesTable.ZoneType == ZoneType::Big
{
info(strFmt("%1, %2, %3, %4", zonesTable.ZoneId,
zonesTable.ZoneType, truckTable.TruckId,
truckTable.Description));
}
}
Output:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 205
that satisfy the criteria.
Exists Join: Following is the behavior of
exists join:
2 or more tables are joined with exists
join using exists join keyword. You use
the keyword exists in front of join
keyword.
Exists join fetch records from one table
whenever a value exists in a common
field in related table i.e. exists join
combine records from one table
whenever a value exists in a common
field in another table.
The records are retrieved only from
parent table. This will not fetch the
records from all the tables as this join is
used only to check existence of a record
in child table. For example, let us
consider a scenario where you like to
get list of customers who has done at
least one transaction. In this case, you
dont need how many transactions are
there and what are they and probably
need only the customers.
The parent record is populated only
once even if there are multiple
matching child records.
You have to add a relation using the
where clause while using exists join.
You can also add multiple filters in
where clause in addition to the existing
one.
Please observe the output of the
program to understand how exists join
will return the results.
In the output, you can see the results
extracted from only one table joined,
ZonesTable even if there are multiple
matching records existing in TruckTable
and the second table i.e. TruckTable is
not populated, which is resulting blank
static void ExistsJoinExample(Args _args)
{
ZonesTable zonesTable;
TruckTable truckTable;
info("With no extra filters in where clause");
while select zonesTable exists join truckTable where
zonesTable.ZoneId == truckTable.ZoneId
{
info(strFmt("%1, %2, %3, %4", zonesTable.ZoneId,
zonesTable.ZoneType, truckTable.TruckId,
truckTable.Description));
}
info("With an extra filter in where clause");
while select zonesTable exists join truckTable where
zonesTable.ZoneId == truckTable.ZoneId &&
zonesTable.ZoneType == ZoneType::Big
{
info(strFmt("%1, %2, %3, %4", zonesTable.ZoneId,
zonesTable.ZoneType, truckTable.TruckId,
truckTable.Description));
}
}
Output:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 206
values if tried to access them.
This join is mostly used in cases where
you need to fetch the records from one
table that are joined using a related
field and you need to traverse through
all the records of one table that satisfy
the criteria checking for existence of
related record in child table.
NotExists Join: Following is the behavior of
not exists join:
2 or more tables are joined with not
exists join using notExists join keyword.
You use the keyword notExists in front
of join keyword.
NotExists join fetch records from one
table whenever a value does not exist
in a common field in related table i.e.
not exists join combine records from
one table whenever a value in a
common field in another table does not
exist.
The records are retrieved only from
parent table. This will not fetch the
records from all the tables as this join is
used only to check existence of a record
in child table and if it does not exist,
fetch the record and skip if exists. For
example, let us consider a scenario
where you like to get list of customers
who doesnt have even one transaction
i.e. there is not even one matching
record in child table. In this case, as
there are no records in child table,
there is no question of fetching records
from related table and you use
notExists join for this.
You have to add a relation using the
where clause while using notExists join.
You can also add multiple filters in
where clause in addition to the existing
one based on your requirement.
Static void NotExistsJoinExample(Args _args)
{
ZonesTable zonesTable;
TruckTable truckTable;
while select zonesTable notExists join truckTable where
zonesTable.ZoneId == truckTable.ZoneId
{
info(strFmt("%1, %2, %3, %4", zonesTable.ZoneId,
zonesTable.ZoneType, truckTable.TruckId,
truckTable.Description));
}
}
Output:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 207
Please observe the output of the
program to understand how notExists
join will return the results.
In the output, you can see the results
extracted from only one table joined,
ZonesTable and there is no point of
records in TruckTable, which is a child
table as it will not have the records
anyways, which will result in blank
values if we try to access them. And,
finally from the output, you can see
only records which dont have matching
related records as displayed in the
infolog.
This join is mostly used in cases where
you need to fetch the records from one
table that is joined using a related field.
You can also use this join to traverse
through all the records of one table that
satisfies the criteria checking for
unavailability of related record in child
table.
Outer Join: Following is the behavior of
outer join:
2 or more tables are joined with outer
join using outer join keyword. You use
the keyword outer in front of join
keyword.
Outer join combine records into one
table even if there are no matching
values in a common field i.e. regardless
of the existence of matching value, the
records from parent table are returned
in the query. (Note that a relation is
used or you will get records joining both
which will result unexpected result and
return a large number of records with
combinations).
The records are retrieved from all the
tables joined. For the records that have
matching values in the common field,
static void OuterJoinExample(Args _args)
{
ZonesTable zonesTable;
TruckTable truckTable;
while select zonesTable order by zonesTable.ZoneId outer
join truckTable where zonesTable.ZoneId ==
truckTable.ZoneId
{
info(strFmt("%1, %2, %3, %4", zonesTable.ZoneId,
zonesTable.ZoneType, truckTable.TruckId,
truckTable.Description));
}
}
Output:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 208
both the tables are populated and for
the records that dont have matching
values in the common field, only parent
table is populated as there is no
question of populating the child table
when there is no record existing in child
table. In the example given, the records
are populated from ZonesTable and
TruckTable also for which they are
available.
If there are more than one related
records in child table, the parent
records are populated multiple times
which will be equivalent to the count of
the related records in child table.
You have to add a relation using the
where clause while using inner join.
You can also add multiple filters in
where clause in addition to the existing
one.
Please observe the output of the
program to understand how the outer
join will return the results.
In the output, you can see the results
extracted from two tables joined. Note
that the record of ZonesTable is taken
multiple times as there are 2 matching
records in the TruckTable and the
records which dont have matching
records in related table are also
populated in the parent table. Finally,
you will get all the records from parent
table and only matching records from
child table as shown in the output.
This join is mostly used in cases where
you need to fetch all the records from
all the tables that are joined using a
related field and you need to traverse
through all the records of both tables
that satisfy the given criteria. Usually,
this join is used to find the records of
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 209
parent table and child that have related
records in child table matching given
criteria and also the parent table
records which dont have matching
child records.
A small note is, if you do keen
observation, you can find the following:
Outer Join = Inner Join + NotExists Join
With an enough explanation about joins and inline queries, we will move to the next step in
queries, AOT Queries as follows.
AOT Queries
Inline queries are created for particular purpose in the code directly. If you have a requirement
to create and use a standard query that will be used in multiple places throughout programming in AOT,
you can go for AOT queries. AOT queries are created under Queries node in AOT and are available in all
the nodes in AOT in any method and in any code part. An AOT query is an object-oriented interface to
the SQL database. A query is composed of objects from different classes from query framework. Various
objects are available to manipulate a query object from the AOT through X++ program.Query framework
has nearly 8 classes that are used to create, modify, update queries, link other queries and filter queries.
The following image describes how the Query framework looks and works with the classes available [The
below image is taken from MSDN which shows Query object model].
The below image shows the system classes used while designing Query. These are used by AX
environment while creating AOT queries, but, you see them in action while creating X++ queries
practically using code. You can understand the classes using the hierarchy and the suffix after the class
name indicates how many objects of that particular class type can be attached to its parent object. (1)
indicates that only one object of that particular type can be attached to its parent object whereas (n)
indicates that any number of objects can be attached.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 210
The following table explains about each class in the framework in detail:
QueryRun This class is used to execute a static or dynamic X++ query. This is also used
to fetch data with the built in methods. You can traverse through the
records that are retrieved after executing a query using this class.
Query This class is used to create the query. This is the class that stores the
definition of the query and is used in QueryRun. This will have one or more
data sources in which tables are added to fetch the data from.
QueryBuildDataSource This class is used to define or access a single data source in the query. If you
add multiple data sources to a query at same level, the queries are
executed sequentially in separate SQL statements and you will see the
result one after the other. You can add a data source in another data
source. In this case, a join is created between parent and child data sources
and the result will be based on the join that is created as shown in the
previous sections.
QueryBuildFieldList This class is used to define the list of fields that are returned from the
database table when a query is executed. Developer can specify the list of
fields needed from the data source or can all the fields dynamically. Each
data source can have only one field list and is represented using
QueryRun
Query
QueryBuildDataSource (n)
QueryBuildFieldList (1)
QueryBuildRange (n)
QueryBuildDynaLink (n)
QueryBuildDataSource (n)
QueryBuildFieldList (1)
QueryBuildRange (n)
QueryBuildLink (n)
QueryBuildDataSource (n)
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 211
QueryBuildFieldList object. You can also specify aggregate functions on the
fields.
QueryBuildRange This is used to add criteria based on which the records are filtered. This
works just like where clause. You can add any number of ranges to add
multiple filters which will be ANDed to the data source for multiple ranges.
QueryBuildDynaLink This is used to store information about a relation to an external record. This
will exist only on outer data source or parent data source of the query. The
information in this object is converted into additional entries in where
clause of the SQL statement created by query.
QueryBuildLink This is used to specify the relation between two data sources and is
available in child data source. This object will have the information specific
to the data source and its parent data source.
QueryFilter This object is explicitly used to filter the results when an outer join is used
between 2 tables. This filters the parent data table records and will take
effect after QueryBuildRange filter is applied.
With enough information about all the system classes that can be used in building queries, its
time to apply and see them in practice. The following steps will show how to create an AOT query in
practice:
To create an AOT query, first you should have table(s). We use the ZonesTable and
TruckTable created in previous sections of this book.
Find the Queries node in AOT, right click on Queries node and click on New > Query.
In the properties of the query just created, set the Name to ZonesTable and optionally,
set the Title and Description also.
You have 2 interesting properties, QueryType and AllowCrossCompany keywords.
QueryType is used to specify the association between 2 or more data sources. The
values can be either Join or Union. Join works as discussed in the above sections. Union
is used to combine data sources but may need some rules to be followed. Property
AllowCrossCompany is used for fetching records from multiple companies. You can do
this instead of using cross company keyword.
Now, its time to add the data source to the Query. Drag and drop the table into the
Data Sources node or right click and create a new data source and select the Table
property of the data source. In our case, this is ZonesTable. The name of the data source
will be taken as ZonesTable_1. You can change or keep it as it is. This convention is used
as we may add the same table again to the data sources which may collide if the same
name is used.
Expand the ZonesTable data source just created, you will find a node called Fields. This
is used to add the list of fields to the data source. In the properties of this node, set Yes
to the property Dynamic. This is used to select all the fields by default. Note that setting
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 212
the Dynamic property value to either Yes or No is mandatory and you will get an error if
you dont set the property value or it is Unselected.
Setting Dynamic property to Yes makes maintenance easier because you don't need to
change the query if a field is changed in the underlying data source but, restricting the
fields returned by the query is better for performance because unused data is not
returned to the client each time the query is run.
Property Dynamic to Yes will be enough for most of the cases, but you can also add the
fields manually. To add the fields manually, right click on the Fields node, click on New >
Field. In the properties of the Field created, select the Field property accordingly. You
can even add the aggregate methods in the same way which will be found in list when
you select New. Usually, new fields are added with Dynamics property set to No.
Now, your basic query is ready. The query is just created with a single data source. You
can find few more nodes in the data sources which can be used to add ranges or sorting
techniques etc. to the data source. You can also override methods in the Query in the
methods node. We will take these in next exercise after learning how to use this query.
The query just created can be used in various places like forms, reports, views, other
queries etc. We will use this query to create a view. To do this, just create a view, drag
and drop the ZonesTable query into Meta Data node of the view. Drag and drop the
required fields from the data source to the fields node of view. Save the query and
open. You can see the view populated with the records.
The main use is, you can use this query in multiple
objects at a time without creating multiple times
and if you modify or update the query, that will
reflect everywhere. To identify where you are
using the query, for every AOT query you create, it
has a node called Dependent Objects, which will
have the list of the objects where you used the query as seen in the screen shot.
The following program demonstrates how to use the query created in AOT:
static void UseAOTQuery(Args _args)
{
Query query = new Query(queryStr(ZonesTable)); //Step 1
QueryRun qRun;
ZonesTable zonesTable;
qRun = new QueryRun(query); //Step 2
if(qRun.prompt()) //Step 3
{
while(qRun.next()) //Step 4
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 213
zonesTable = qRun.get(tableNum(ZonesTable)); //Step 5
info(strFmt("%1, %2", zonesTable.ZoneId, zonesTable.Description));
}
}
}
Few points on how to access the query using X++ code:
Instantiate Query object with the AOT query as done in Step 1. Note that queryStr() is
used to identify the query. You need to pass query name to that.
Instantiate QueryRun object as did in Step 2. Pass query object into QueryRun()
constructor. QueryRun will use this query to fetch the records from database table.
Display the query form for user to give any input for ranges (if added to query) as did in
Step 3. This form is called as SysQueryForm. To suppress this, you can set the Interactive
property of query to No, which will not display this form. Interactive is also used to
determine whether users can interact with the report by delimiting queries and setting
printer options and so on. The form looks as follows:
You can add new ranges and sorting fields using this form with the button Add and
Remove on the form and from the Sorting tab you see in the form. Once you click OK,
control enters into Step 5, which is a loop that fetch records one by one executing the
query. Note that clicking on Cancel will not enter into the block and skip the block. The
next() method of QueryRun will retrieve the next() record from the query.
To use the record that is there in query, you retrieve the record from the query using
get() or getNo() method of the QueryRun object. You can find the usage of get() in Step
5, where the table id is passed. You can also use getNo() to do the same. The difference
is get() will retrieve record based on table name whereas getNo() will retrieve based on
the order the data sources added to Query. In both cases, the current record is returned
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 214
and should be caught into a table buffer. This can be used when you use same table
more than 1 time in same query. The following statement shows how to use getNo():
zonesTable = qRun.getNo(1);
Use the property UniqueId of the data source to find the order of data sources added to
your query which can be used to get the record from the query. The other is get(), can
be used as follows, which will identify the data source that should be returned using the
table id:
zonesTable = qRun.get (tableNum(ZonesTable));
Once you get the table buffer, you can use that as per your requirement.
With the information on how to create and use AOT queries, lets try to add ranges, sorting
options and other stuff to the Query just created.
To add ranges to query, follow the steps below:
Expand the query ZonesTable, click Data Sources, and then expand the data source.
Right click Ranges, and then click New Range.
Right click the new range, click Properties, and then select a field in the Field property
list.
Type an expression in the Value property to specify which records are retrieved. You can
add values to ranges at runtime or in SysQueryForm.
The same procedure can be followed to add ranges to any query. Find the query you
need to add ranges and follow the above steps.
Note that, ranges expressions on same field if defined multiple times are OR'ed and for
different fields is ANDed.
There are few interesting properties of range which can be used as follows:
o Value: Used to set the initial value to the range. You can use the symbols =
(Equals), ! (Not), .. (From..to, like 10..30 will return from 10 to 30), > (Greater
than), < (Less than), * and ? (Wild card characters * for any number and ? for
one character), , (and).
o Status: Open, Lock and Hide are the values in which Open will allow user to set
new values or change existing value using SysQueryForm, Lock will make user
able to see the value but unable to set or change the value and Hide will hide
the range in SysQueryForm to the user but can be changed using X++ code.
o Enabled: This property of range is used to either enable or disable the range
defined.
Note: Optimal number of ranges will make query better whereas, large number of filters may
impact the query performance.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 215
To add Order By to query, follow the below steps:
Navigate to AOT>Queries>ZonesTable >Data Sources>ZonesTable_1 (This path is for the
query just created. Follow the steps for your own query).
Expand the Fields node.
Drag any field onto the Order By node. You can also right click on Order By can click on
New Field and set the properties accordingly.
In the properties window for the Direction property, you have the option of changing
the default from Ascending to Descending.
To add Group By to query, follow the below steps:
Navigate to AOT>Queries>ZonesTable >Data Sources>ZonesTable_1 (This is for current
query. Please follow the path for finding your query).
Expand the Fields node.
Drag any field onto the Group By node. Please note that group by is used most probably
with some aggregate function.
Following is the updated program which will add ranges using X++ code to the query:
static void UseAOTQuery(Args _args)
{
Query query = new Query(queryStr(ZonesTable)); //Step 1
QueryRun qRun;
ZonesTable zonesTable;
query.dataSourceNo(1).range(1).value(SysQuery::value(enum2str(ZoneType::Medium)))
; //Step 1.1
qRun = new QueryRun(query); //Step 2
if(qRun.prompt()) //Step 3
{
while(qRun.next()) //Step 4
{
zonesTable = qRun.getNo(1); //Step 5
info(strFmt("%1, %2", zonesTable.ZoneId, zonesTable.Description));
}
}
}
In Step 1.1, all the steps are done at once. Instead, we first get the data source object and store
into QueryBuildDataSource reference, get the QueryBuildRange object from data source and store into
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 216
QueryBuildRange reference and add the value to that range using the method value(). Dont worry, for
time being use this. You will do this again step by step in X++ queries. Finally, SysQuery::value() is used
to assign an atomic value to query range programmatically. You can also use the methods from class
SysQueryRangeUtil like SysQueryRangeUtil::currentDate(), SysQueryRangeUtil::currentUserId() etc. to
set the values to ranges directly. But there is a potential risk involved in it, if your X++ code is compiled
to .NET Framework CIL, and is then run as CIL, your code might cause an error as these will find the
value at runtime for e.g. based on current user logged in for SysQueryRangeUtil::currentUserId(). You
may also use expressions in ranges like ((A == 10) || (B == 20)). I leave this to you for doing exercise on
how to use the expressions in ranges. Try a range like ((CustGroup == "20") || (Currency == "USD")) and
check the output. We will discuss more about CIL in appendixes.
Note: If you like to retain its state in SysQuery form for multiple user runs, you have to set the property
UserUpdate to Yes i.e. if a user opens the query form and update values to ranges etc. they will be saved
and when user open the SysQuery form next time, they will be restored which may save time and effort
in cases where user may need only few values very frequently.
With good information and exercises on simple queries, its time to move to more complex
scenario, where you need to add multiple data sources to a single query. The scenario for this is, retrieve
all the Trucks which are related to particular zone wise. To add multiple data sources, follow the below
steps:
Navigate to AOT>Queries>ZonesTable >Data Sources>ZonesTable_1 > Data Sources (This
is for current query. Please follow the path for finding your query).
Right click on the Data Sources you found for the parent data source and click on New
Data Source to add child data source.
Set TruckTable in the properties of the data source just created. You can also drag and
drop the table onto this data source node which will create the data source with the
properties automatically. You can even add the child data source for the current child
data source which will increase the levels.
Expand the child data source and set the property Dynamic of node Fields to Yes. This
will populate all the fields into the node Fields of your new child data source.
You can add ranges as discussed for the earlier data source in Ranges node. Try adding
TruckType, which we will use while calling the query using program.
Finally, you can see the node relations.
You can add relations between the current
data source and its parent data source
using this node. Just right click on relations
and click on New Relation. Manually, set
the properties JoinDataSource, Field and
RelatedField of newly created relation. For
more information, please check the image
given. You can also set the property
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 217
JoinRelation of the child data source after selecting the JoinDataSource to relate the
fields automatically based on table relations [please follow the steps while reading for
better understanding]. Note that, the default join taken between the data sources when
you create, will be Inner Join. You can change this at any time. You can also apply the
existing table relations to the child data source by setting the property Relations of child
table to Yes. This will automatically get the table relations between the child data source
and its parent data source and populate and apply them under Relations node of child
data source.
Now, lets understand few important and interesting properties on data sources:
Company This will determine from which company the data should be retrieved.
This can be used in few cases where you need to retrieve the data only
from one company.
FirstOnly If set to Yes, this will retrieve only the first record from the query. Be
cautious of using this. You may use this to increase performance when
you need only one record.
FirstFast If the value of this property is set to Yes, the first record from the query
will be retrieved fast which can be used for optimizing the performance
of record retrieval. You can find more about firstFast in the Inline
Queries section of this chapter.
AllowAdd This is used to determine whether users can add fields to sorting and to
ranges at run time. Usually, this is set to All fields which means that you
can add the fields. You have another value, No fields, which if set, you
cannot add fields to sorting and ranges and the buttons Add and
Remove of SysQueryForm will be disabled.
FetchMode This is used to specify whether the data sources should be related
through a 1:1 relation or a 1:n relation. You will find this only on child
data sources. The first parent, which is main data source will not have
this property.
JoinMode Use this to set the join that should be used between two data sources
for querying results. More information on Joins can be found in Inline
queries section of this chapter. Joins work same with a small difference
that, we hard code there, here, it is a property you set. This will
determine the strategy for how to join the output from a data source.
Please note that you will find this only on child data sources. The first
parent, which is main data source will not have this property.
Update This will determine whether the query is allowed to update records in
the database, like the usage of forUpdate in inline queries. Most
probably, we dont set this property, instead, we select the records
using inline queries to update on the fly using forUpdate and TTS.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 218
Relations This will specify if the query system should use relations defined on
tables should be used in queries or not. As discussed while creating
multiple data sources, Yes will import table relations into query and No
will not and if this property is set to Yes, the query is automatically
updated if a relation is changed. If you use No, you have to manually
create relations in the query between the data sources which may
increase time and effort. In AX 2009, this will also search for EDT
relation which are obsolete in AX 2012. Please note that you will find
this only on child data sources as relation is done with parent
table/data source by child table/data source. The first parent, which is
main data source will not have this property.
Enabled This property is used to specify whether the data source should be
considered or not. If set to No, the data source and all embedded data
sources are ignored. Please be aware before setting the property to
avoid unwanted results as you may not get the output of the data
sources and its child data sources in query if you disable a data source.
ConcurrencyModel This is used to set the concurrency model that should be used by Query
while fetching records. To know more about the concurrency models
and the support in AX, please refer to Inline Queries section of this
chapter.
The following program will show you how to use the query that has two or more data sources.
This will be simulated using X++ queries also:
static void UseAOTQueryMultipleDataSources(Args _args)
{
Query query = new Query(queryStr(ZonesTable)); //Step 1
QueryRun queryRun;
QueryBuildRange queryBuildRange;
ZonesTable zonesTable;
TruckTable truckTable;
queryBuildRange = query.dataSourceNo(1).range(1); //Step 2
queryBuildRange.value(SysQuery::value(enum2str(ZoneType::Big))); //Step 3
query.dataSourceNo(2).range(1).value(SysQuery::value(enum2str(TruckType::Small)));//Step 4
queryRun = new QueryRun(query); //Step 5
if(queryRun.prompt()) //Step 6
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 219
while(queryRun.next()) //Step 7
{
zonesTable = queryRun.getNo(1); //Step 8
truckTable = queryRun.get(tableNum(truckTable)); //Step 9
info(strFmt("%1, %2, %3, %4", zonesTable.ZoneId, zonesTable.Description,
truckTable.TruckId, truckTable.Description));
}
}
}
From the above program, you can observe the following things:
If you observe Step 2, you can find how the ranges can be added. This is the way you use
to add ranges and the values are added like it is done in Step 3. You can observe the
difference between Step 2 and Step 4. In Step 4, range is not taken but directly, the
value is added to the query data source.
Step 8 and Step 9 are used to retrieve the record from the query. Note the difference
between 2 variants, one uses getNo() which will get the data based on order of data
sources added to the query. The difference between 2 variants is discussed in previous
example. You can refer that example for more details.
If you observe the output, you will get the output of two tables joined and the output
will depend on the range values and join you set. As we didnt set any join, the default
join taken will be Inner Join and you will get the combined records of the tables based
on inner join rules.
You can change the join types between data sources and check the output for
difference.
Unions in Queries
As we discussed in earlier section, in addition to Joins, you can also use Unions in AOT Queries.
Union will combine two or more data sources with some constraints applicable while using the Unions in
AOT Queries. Below is a list of constraints that should be considered by the developer:
While using Unions, all data sources you use in the query should have same structure
with same number of columns and the corresponding columns of each data source must
be of same data type. This constraint is there because when you use Union, this will
merge the two record sets into one and while you execute query, you will get results
one after another appended and the number and type may create collision if this is not
considered as Unions will not support any kind of implicit or explicit conversions
between data types.
Note that the Union always uses the column names of first data source as column
names for the query which cannot be changed and sorting is done based on the first
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 220
data source order by clause and unions will ignore the order by clauses other than first
data source.
Queries with Unions cannot be used to update the records like other queries. By default,
the property Update of all the data sources in a union query is set to No and cannot be
changed.
The following exercise demonstrates how to use Union queries. Here, only one table is
taken to make sure that the above mentioned constraints work. You can try with
different tables which have similarity in structure and have a trial:
o Create a simple AOT Query as mentioned in earlier exercise and name it
ZonesTableUnion. Set the property QueryType of the query to Union.
o Add ZonesTable as data source in the ZonesTableUnion query. The name of the
data source looks like ZonesTable_1. Add ZonesTable again at the same level.
The name of data source looks like ZonesTable_2. Now add ranges to both the
data sources. For time being, take ZoneType as your range.
o You can find an interesting property in the data source ZonesTable_2,
UnionType. This property has two values namely, Union and UnionAll. Union will
eliminate duplicate records when the records are combined and return unique
records whereas UnionAll will include duplicate records also. For time being, set
the property value to Union, to get the duplicate records and save the query.
o Once everything is done, check how the union works with the following
program:
static void UseAOTQueryUnion(Args _args)
{
Query query = new Query(queryStr(ZonesTableUnion)); //Step 1
QueryRun qRun;
ZonesTable zonesTable;
query.dataSourceNo(1).range(1).value(SysQuery::value(enum2str(ZoneType::Medium)))
; //Step 1.1
query.dataSourceNo(2).range(1).value(SysQuery::value(enum2str(ZoneType::Small)));
//Step 1.2
qRun = new QueryRun(query); //Step 2
if(qRun.prompt()) //Step 3
{
while(qRun.next()) //Step 4
{
zonesTable = qRun.getNo(1); //Step 5
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 221
info(strFmt("%1, %2", zonesTable.ZoneId, zonesTable.Description));
}
}
}
o When you run the query, you will see the following query form as discussed
earlier:
o You can see multiple data sources added with ranges etc. in the above image.
Click on OK and check the output. You may not find duplicates as each data
source is using different range which will result unique record set. Try removing
the ranges and check the output. You should see the same output twice.
o Now, try creating a Union query for customer table and vendor table which will
have similar fields. Create fields on your own in data sources of query to find the
similar fields. As you know, if you set property Dynamic to Yes, it will get all the
fields which may not be equal in all the data sources all the times. You can also
add these queries in forms, views etc. objects to check the output.
Composite Queries
Composite query is a query created from another query i.e. you can create a new query out of
existing query which is called as composite query. You may get a doubt why you need to create or
extend a query when one is there. The answer is reusability. Let us consider a scenario where you may
need to override a method or need to add few extra ranges to an existing query without disturbing the
functionality developed using existing query at multiple places. You may think that you should have
some inheritance kind of facility to extend an existing query which will solve your requirement. To solve
this kind of requirement, you can go for composite queries. Follow the steps to create a composite
query:
Create a normal AOT query and give a name to that.
Drag and drop the query you like to extend the features of.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 222
The query you just created is called as composite query and the actual query used by
you to create composite query is called as base query.
You can override new methods or add ranges in composite query and this will not reflect the
base query and objects using base query. But, if you update the base query, it will certainly reflect the
composite query. Note that, under a query node, either the Data Sources or the Composite Query node
can have an object defined, but not both. So if you add a query in composite query node, you cannot
add any other data sources to that query. If you override a method on composite query, the same
method is called in the base query similar to method calls in inheritance. For more information about
inheritance, please refer to the Classes topic of this book.
Query Duplication/AOT Object duplication
You can create a duplicate of any query at any point of time to modify/update the query and use
that in other objects. To do this, just right click and click on Duplicate. In fact you can duplicate most of
the objects in AOT. Duplicating an object will create the object with similar structure but not data [This is
the case with tables]. The name given will be similar to CopyOf<Object Name> . It is recommended to
update the name as per your requirement. Please note that there will be no relationship between the
original and duplicate objects. Both the objects are individual and change/update of one will not reflect
other in any way in structure until and unless if they are related by user like in tables with table relations
etc.
Advantages of AOT Queries:
Following are a few advantages of using AOT queries:
Though it is possible to build complex queries which can fetch the data in data sets,
forms, reports and views, AOT queries are most powerful as SQL statements. This also
support reusability of one query in multiple objects i.e. design one query and use that in
any number of objects like forms, views etc. that needs same data based on your
requirement.
Query Changes are Instantly in Effect for Consuming Objects
Query Cannot be Combined with Other Data Sources i.e. when you use query as data
source on forms or other types of objects, you cannot attach any other objects like
tables or views as data source which will be an added advantage few times.
An AOT Query developed for a particular object like a customer report can be used by
customer form if required in few business cases. This will reduce your effort and work
and any modification for the query can be done in one place which will reflect in all
places instead of updating every object.
The changes that are done for a query in one place will reflect in all the dependent
objects using that query.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 223
As the AOT queries serve a large number of purposes, you may need to design and
develop the same kind of queries on the fly using code. These queries are called as X++
queries. Now, its time to look into the details of X++ queries as follows.
X++ Queries
You can also create the queries using the query framework used in AOT queries for creating
queries using X++ code. This is just like a simulation of AOT Query in X++. This section focuses on the
same where you will see how to create and work with X++ queries with all the facilities available and the
possibilities with X++ queries compared with AOT queries.
Lets build a simple query using X++ with a single data source. Before going for X++ program,
recall the steps of creating X++ query. Steps are very simple, create a Query, add data source, add
ranges and run the query. Now, its time to take a sample, observe the following program:
static void SimpleXPPQuery(Args _args)
{
Query zonesTableQuery;
QueryBuildDataSource zonesTableDataSource;
QueryBuildRange zoneTypeRange;
QueryRun qRun;
ZonesTable zonesTable;
zonesTableQuery = new Query(); //Step 1
zonesTableDataSource = zonesTableQuery.addDataSource(tableNum(ZonesTable)); //Step 2
zoneTypeRange = zonesTableDataSource.addRange(fieldNum(ZonesTable, ZoneType)); //Step 3
zoneTypeRange.value(SysQuery::value(enum2str(ZoneType::Big))); //Step 4
zonesTableDataSource.addOrderByField(fieldNum(ZonesTable,ZoneId), SortOrder::Descending);
//Step 5
qRun = new QueryRun(zonesTableQuery); //Step 6
if(qRun.prompt()) //Step 7
{
while(qRun.next()) //Step 8
{
zonesTable = qRun.getNo(1); //Step 9
info(strFmt("%1, %2, %3", zonesTable.ZoneId, zonesTable.Description,
zonesTable.ZoneType));
}
}
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 224
With a crystal clear example, lets dig into each step as follows:
Step 1: Create a new query using Query class. Just create an object. This should not
refer any AOT query as did in past examples.
Step 2: Add the data source to the query. This is done using addDataSource() method of
query. You can add multiple data sources optionally at same level or different level
which will be discussed in coming text. Just pass the table id of the table you like to use
as data source. To get the table id, use tableNum() method. You can optionally pass few
other values into the method addDataSource(), name of query, union type etc. you can
check the syntax with intellisense. Once the data source is added, the method will
return QueryBuildDataSource object which is caught for further use as you can see in
the program.
Step 3: Adding range is done to the data source. You can add any number of ranges.
Ranges are added using the addRange() method of the data source object. Note that
you stored the data source object returned by addDataSource() method which is used
now. To add the ranges, just pass the field id. Getting field id is similar as getting table
id. Use the method fieldNum() to get the field id. You can check for optional values in
method addRange() and use them though not mandatory in few cases. Once you are
done adding range, for each range you add, you will get an object of type
QueryBuildRange. You can catch this object for further use in program like assigning
values to the ranges etc.
Step 4: Adding values to ranges is done using the method value() of range object which
is caught in last step. Just pass the value in the format of string or you can optionally use
the method SysQuery::value(). To know more about using this, you can check AOT
queries section. SysQueryRangeUtil can also be used which is discussed earlier.
Step 5: Adding order by field is done using the method addOrderByField() of data source
object. Just pass the field id and the sorting order you like to use for order of sorting the
records.
Step 6: Just create a QueryRun object with the query created.
Step 7: Though not mandatory, prompt user with SysQueryForm. If you dont like to
display this form or it is not required for user, just comment the line and proceed.
Step 8: Get the record one by one, present to the user by fetching the record using
QueryRun object.
Step 9. You can use get() or getNo() for fetching records as did in earlier examples.
Now, you are able to understand how to program X++ queries. Just remember few classes of
Query Framework. Its very similar to creating AOT query. Follow the steps in order which will make you
create your own queries in AOT or X++. Note that, list of fields is not selected explicitly, which will fetch
all the fields of the table from underlying database table. With a small and beautiful example, its time
to move to complex things as follows.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 225
static void SimpleXPPQuery(Args _args)
{
Query zonesTableQuery;
QueryBuildDataSource zonesTableDataSource;
QueryBuildRange zoneTypeRange;
QueryBuildFieldList zonesTableFieldList;
QueryRun qRun;
ZonesTable zonesTable;
zonesTableQuery = new Query(); //Step 1
zonesTableDataSource = zonesTableQuery.addDataSource(tableNum(ZonesTable)); //Step 2
zonesTableDataSource.name("ZonesTable"); //Step 2.1
zonesTableFieldList = zonesTableDataSource.fields(); //Step 3
zonesTableFieldList.addField(fieldNum(ZonesTable, ZoneId)); //Step 3
zonesTableFieldList.addField(fieldNum(ZonesTable, ZoneType));
zonesTableDataSource.addRange(fieldNum(ZonesTable, ZoneId)).value(strFmt('((%1.%2 ==
"Z1004") || (%1.%3 == ZoneType::Big))',
zonesTableDataSource.name(),
fieldStr(ZonesTable, ZoneId),
fieldStr(ZonesTable, ZoneType))); //Step 4
zonesTableDataSource.addOrderByField(fieldNum(ZonesTable, ZoneId),
SortOrder::Descending); //Step 5
qRun = new QueryRun(zonesTableQuery); //Step 6
while(qRun.next()) //Step 7
{
zonesTable = qRun.getNo(1); //Step 8
info(strFmt("%1, %2, %3", zonesTable.ZoneId, zonesTable.Description,
zonesTable.ZoneType));
}
}
Notice the Step 2.1, where the name is added to data source. The name() method of data source
object can be used to add the name to the data source. To add only the required fields to the query, just
get the QueryBuildFieldList object using fields() method of data source. Now, add the fields one by one
to the QueryBuildFieldList object using the method addField(). You can add as many fields you need. Just
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 226
pass field id to that, which can be seen in step 3. In the above example, only ZoneId and ZoneType fields
are added. So, when you use zonesTable.Description, you dont see any value. You will see a blank value
always as the column is not selected. Step 4 shows you how to add expressions as ranges. Here, an
expression which will check for the value of ZoneId ORed with ZoneType can be seen. Note that, value is
assigned using value method and not by using SysQuery::value().Expressions in query ranges will be
evaluated at runtime. Any wrong statements will reflect output and may result unwanted results. You
can check how the table names and field names are handled using notation %1.%2 with strFmt(). This is
mostly used in case if you use different data sources and you need the values from different data
sources in expressions. Rest everything is same as other queries and previous code samples. Please note
that the records are always displayed in infolog which is not mandatory. You can instead use this data to
generate reports or perform some other operations. Optionally, you can add group by in your program
as shown in the following example:
static void SimpleXPPQuery(Args _args)
{
Query zonesTableQuery;
QueryBuildDataSource zonesTableDataSource;
QueryBuildRange zoneTypeRange;
QueryBuildFieldList zonesTableFieldList;
QueryRun qRun;
ZonesTable zonesTable;
zonesTableQuery = new Query(); //Step 1
zonesTableDataSource = zonesTableQuery.addDataSource(tableNum(ZonesTable)); //Step 2
zonesTableDataSource.addSelectionField(fieldNum(ZonesTable, ZoneId),
SelectionField::Count); //Step 3
zonesTableDataSource.addGroupByField(fieldNum(ZonesTable, ZoneType)); //Step 4
zonesTableDataSource.orderMode(OrderMode::GroupBy); //Step 5
qRun = new QueryRun(zonesTableQuery); //Step 6
while(qRun.next()) //Step 7
{
zonesTable = qRun.getNo(1); //Step 8
info(strFmt("%1, %2, %3", zonesTable.ZoneId, zonesTable.ZoneType));
}
}
Just check the steps 3,4 and 5 to understand how aggregates are used in X++ queries. The
selection field is added with ZoneId for which aggregate should be done in step 3. Grouping field is
added in step 4 where the field is used to group records using addGroupByField() of data source. You
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 227
can optionally specify the order mode using the orderMode() method of data source which will not
make any difference even if you omit for the grouping requirement like this. Comment the line step 5
and check the output, you should see the same. The following program will show you how to add
multiple data sources with some advanced actions like joins etc. using X++ code:
static void MultiDataSourceSimpleXPPQuery(Args _args)
{
Query zonesTableQuery;
QueryBuildDataSource zonesTableDataSource;
QueryBuildDataSource truckTableDataSource;
QueryBuildRange zoneIdRange, truckIdRange;
QueryBuildLink zoneTruckLink;
QueryRun qRun;
ZonesTable zonesTable;
TruckTable truckTable;
zonesTableQuery = new Query(); //Step 1
zonesTableDataSource = zonesTableQuery.addDataSource(tableNum(ZonesTable)); //Step 2
truckTableDataSource = zonesTableDataSource.addDataSource(tableNum(TruckTable));//Step
2.1
zoneIdRange = zonesTableDataSource.addRange(fieldNum(ZonesTable, ZoneId)); //Step 3
truckIdRange = truckTableDataSource.addRange(fieldNum(TruckTable, TruckId));
truckTableDataSource.relations(true); //Step 4
zoneTruckLink= truckTableDataSource.addLink(fieldNum(ZonesTable, ZoneId),
fieldNum(TruckTable, ZoneId)); //Step 5
truckTableDataSource.joinMode(JoinMode::OuterJoin); //Step 6
qRun = new QueryRun(zonesTableQuery); //Step 7
while(qRun.next()) //Step 8
{
zonesTable = qRun.getNo(1); //Step 9
truckTable = qRun.getNo(2);
info(strFmt("%1, %2, %3, %4", zonesTable.ZoneId, zonesTable.ZoneType,
truckTable.TruckId, truckTable.Description));
}
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 228
Just check the output, you should be able to see the outer join applied on the tables linked and
you will see the records combined using outer join rules. Adding multiple data sources is very simple
with a small understanding on where to use which data source. You should know at what level you
should add the child data source. In the above example, the child data source, truck table is added to
parent data source zones table. The step 2.1 shows this to you where, a data source is added to another
data source. You can treat them as parent and child data sources. Adding multiple data sources at same
level is also possible which will be described in coming text. You can optionally add ranges as shown in
the step 3.This can be done to all the data sources in the query. Once you are done with adding the data
sources and ranges, adding relations can be done using the relations() method of data source or
addLink() of data source which can be found in step 4 and step 5. Please note child data sources will be
used to add relations with parent data sources as discussed in AOT queries. relations() will add the
relations automatically from underlying table level where as addLink() is a manual operation as did in
AOT queries. Note that you may not always have table relations and your requirement may change. In
such cases, you can opt for addLink(). This method will return the QueryBuildLink object which can be
used further. Finally, specifying join mode is very simple. Just use the JoinMode() method of data
source. Consider the data source you are using while calling this method to set the join mode as join
mode is set on child data source which is linked with parent. Rest remains same, create QueryRun
object, run the query and fetch the results. Consider modifying this query adding order by clause with
some extra advanced features. Now, consider that this is a complex query you have created taking time
and effort. You may need this query multiple times in multiple objects like forms, reports etc. where you
considered this to make into AOT Query. Instead of creating an AOT query manually, you can add this
query to AOT directly using X++ code. The following program shows you how to do this:
static void AddToAOTXPPQuery(Args _args)
{
TreeNode treeQueryNodeObj;
str queryName = "MultiDataSourcesAOTQuery";
Query zonesTableQuery;
QueryBuildDataSource zonesTableDataSource;
QueryBuildDataSource truckTableDataSource;
QueryBuildRange zoneIdRange, truckIdRange;
QueryBuildLink zoneTruckLink;
QueryRun qRun;
ZonesTable zonesTable;
TruckTable truckTable;
#AOT
//Step 1
treeQueryNodeObj = TreeNode::findNode(#QueriesPath);
treeQueryNodeObj = treeQueryNodeObj.AOTfindChild(queryName);
if (treeQueryNodeObj) { treeQueryNodeObj.AOTdelete(); }
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 229
//Step 2
treeQueryNodeObj = TreeNode::findNode(#QueriesPath);
treeQueryNodeObj.AOTadd(queryName);
zonesTableQuery = treeQueryNodeObj.AOTfindChild(queryName);
//Step 3
zonesTableDataSource = zonesTableQuery.addDataSource(tableNum(ZonesTable)); //Step 2
truckTableDataSource = zonesTableDataSource.addDataSource(tableNum(TruckTable));
//Step 4
zoneIdRange = zonesTableDataSource.addRange(fieldNum(ZonesTable, ZoneId)); //Step 3
truckIdRange = truckTableDataSource.addRange(fieldNum(TruckTable, TruckId));
//Step 5
truckTableDataSource.relations(true); //Step 4
zoneTruckLink= truckTableDataSource.addLink(fieldNum(ZonesTable, ZoneId),
fieldNum(TruckTable, ZoneId)); //Step 5
truckTableDataSource.joinMode(JoinMode::OuterJoin); //Step 6
//Step 6
zonesTableQuery.AOTcompile(1);
zonesTableQuery.AOTsave();
qRun = new QueryRun(zonesTableQuery); //Step 7
while(qRun.next()) //Step 8
{
zonesTable = qRun.getNo(1); //Step 9
truckTable = qRun.getNo(2);
info(strFmt("%1, %2, %3, %4", zonesTable.ZoneId, zonesTable.ZoneType,
truckTable.TruckId, truckTable.Description));
}
}
If you check the steps in the above program, step 1 is checking to make sure that query with the
same name doesnt exist in AOT and delete it if that is there, as you will get an error if you try to create a
new query in AOT with the name of existing query. Step 2 create the query and get that query object
into your query reference. After step 2, you are bound to an AOT query in your code. Please note that
the modifications you make on this will reflect on AOT query once you bound to the AOT object. Now,
just add the data sources, ranges and relations etc. which needs to be followed while creating an AOT
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 230
Query. If you observe the above code, you are not
calling the constructor and create new query object
because, you are using the query object of AOT and
you dont need to call any constructors and create
query in code. Its time to save the query. Just
compile that using AOTCompile() method of query
object and save that using AOTSave() of query
object. The rest of the code like executing query and
fetching of records remains same. Once you are
done and executed the job, you should be able to
see an AOT query created which looks like in the
given image. Just check how the unions are handled
in an X++ query as follows:
static void XPPUnionQuery(Args _args)
{
Query query = new Query();
QueryRun queryRun;
ZonesTable zonesTable;
query.queryType(QueryType::Union); //Step 1
query.addDataSource(tableNum(ZonesTable),
identifierstr(ZonesTable_1)).addRange(fieldNum(ZonesTable,
ZoneType)).value(SysQuery::value(enum2str(ZoneType::Small))); //Step 2
query.addDataSource(tableNum(ZonesTable), identifierstr(ZonesTable_2),
UnionType::UnionAll).addRange(fieldNum(ZonesTable,
ZoneType)).value(SysQuery::value(enum2str(ZoneType::Big))); //Step 2.1
queryRun = new QueryRun(query); //Step 3
while (queryRun.next()) //Step 4
{
zonesTable = queryRun.get(tableNum(ZonesTable));
info(strFmt("%1, %2, %3", zonesTable.ZoneId, zonesTable.Description,
zonesTable.ZoneType));
}
}
Just check the step 1 and step 2.1, you can find how the union is created using X++ queries.
When you create a query, set the query type as union and while adding the child data source, specify
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 231
the union type in the addDataSource() method and that will make your query work with a union. You
can check the output to understand how it works. Just check the following program to make an X++
query as composite query. To do this, use the method addBaseQuery() of Query object as in step 1
which will take care of the rest as follows:
static void XPPCompositeQuery(Args _args)
{
Query compositeQuery = new Query();
QueryRun qRun;
ZonesTable zonesTable;
TruckTable truckTable;
compositeQuery.addBaseQuery(queryStr(ZonesTable)); //Step 1
qRun = new QueryRun(compositeQuery);
while(qRun.next())
{
zonesTable = qRun.get(tableNum(ZonesTable));
truckTable = qRun.get(tableNum(TruckTable));
info(strFmt("%1, %2, %3, %4", zonesTable.ZoneId, zonesTable.ZoneType,
truckTable.TruckId, truckTable.Description));
}
}
Following are few notable points that can be considered while using queries:
You can access the query using a menu item like any standard form or report is
accessible. To do this, you have to create a class with main() and create action menu
item. Please read classes section to create a class with main() and menu items section to
know more about menus and menu items.
AX doesnt support the concept of null values. Instead, whenever you get a null value
from an aggregated column in the row returned by SQL, the row is skipped and is not
returned to the user. If somehow in few scenarios if you get a null value, you will get an
exception "Unsupported null value selected from the database." thrown by AX. Though
AX does not support null values, each different data types has its specific value that is
treated like a null value in few circumstances.
You can add paging support to X++ queries optionally. This will return the results of the
query as a collection of subsets known as pages. This will increase performance if you
have a very large volume of data in tables. Paging can be enabled in X++ query using
enablePositionPaging() or enableValueBasedPaging() methods of QueryRun class, which
will enable paging and the types of paging techniques available are
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 232
PositionBasedPaging and ValueBasedPaging, which have different uses. A query
cannot have a disabled data source and should not have its FirstOnly property set to No
if you like to use paging.
Advantages of using X++ Queries:
You may wonder why X++ queries are given when AOT queries are available. Consider you need
to write a complex query that may be required on the fly with few different ranges and other variations
which may change constantly and may need only at one place or couple of places. You can prefer X++
query for this kind of scenario which will give you programming interface to access the most powerful
query framework of AX. Another advantage would be the paging which will have great advantage in few
scenarios with a query result set broken into multiple data sets. And, you can even add the most
complex queries you create to AOT. These will make using X++ queries advantageous in few scenarios.
Insert, Update and Delete operations revisited including optimizing record operations:
This section focuses on the Insert, Update and Delete operations on tables with more coverage
on what and how they will be done from internal point and also covers the topic optimizing which is
very important to increase the performance. The following program will demonstrate insertion into
table:
static void InsertRecords(Args _args)
{
TruckTable truckTable;
//Using insert() to insert records
truckTable.TruckId = "T0005";
truckTable.Description = "Suzuki";
truckTable.DateOfPurchase = today();
truckTable.TruckType = TruckType::Medium;
truckTable.ZoneId = "Z1001";
truckTable.ZoneType = ZoneType::Medium;
truckTable.TruckDimensions[1] = 6;
truckTable.TruckDimensions[2] = 7;
truckTable.TruckDimensions[3] = 140;
truckTable.TruckDimensions[4] = 150;
truckTable.insert(); //Step 1
//Using doInsert() to insert records
truckTable.TruckId = "T0006";
truckTable.Description = "Yamaha";
truckTable.DateOfPurchase = today();
truckTable.TruckType = TruckType::Large;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 233
truckTable.ZoneId = "Z1002";
truckTable.ZoneType = ZoneType::Small;
truckTable.TruckDimensions[1] = 7;
truckTable.TruckDimensions[2] = 8;
truckTable.TruckDimensions[3] = 130;
truckTable.TruckDimensions[4] = 140;
truckTable.doInsert(); //Step 2
}
Just check the difference between step 1 and step 2, there is a small variation while you use
these operations. insert() method will call insert() at table level and insert the record into table whereas,
doInsert() will not call insert() at table level. doInsert() will bypass insert() call. This operation is explicitly
used when the insert() should be skipped, like restoring the backup data using CSV or XML files where
you dont like to perform validations and other stuff. Check one more example where you need to
populate the fields TruckId, Description and Dimensions of TruckTable into the duplicate table,
TmpTruckTable to generate a report or to process data for some other requirement (Just duplicate
TruckTable which will save your time, rename the copy to TmpTruckTable and delete the fields other
than mentioned above. This is not a temporary table but a table used to store data for time being and is
named as above):
static void RegularInsert(Args _args)
{
TruckTable truckTable;
TmpTruckTable tmpTruckTable;
while select truckTable
{
tmpTruckTable.TruckId = truckTable.TruckId;
tmpTruckTable.TruckDimensions[1] = truckTable.TruckDimensions[1];
tmpTruckTable.TruckDimensions[2] = truckTable.TruckDimensions[2];
tmpTruckTable.TruckDimensions[3] = truckTable.TruckDimensions[3];
tmpTruckTable.TruckDimensions[4] = truckTable.TruckDimensions[4];
tmpTruckTable.Description = truckTable.Description;
tmpTruckTable.insert();
}
while select tmpTruckTable
{
info(strFmt("%1, %2, %3", tmpTruckTable.TruckId, tmpTruckTable.Description,
tmpTruckTable.TruckDimensions[1]));
}
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 234
This is very regular insert done without any extra care taken for performance. If you have 1000+
records which should be inserted into table, there will be 1000+ trips to the SQL backend, which will
impact performance of your application a lot. Considering this requirement, you have few more
methods of handling these situations. You can use RecordSortedList or RecordInsertList to do the same
kind of operation i.e. insert bulk records into table, but at once. When you use RecordSortedList or
RecordInsertList, you will add records one by one to the List object and once all the records are added,
you insert them into database. This insert operation is done at once and there will be very less number
of trips to SQL server based on the number of records you add which will improve the performance of
the application. The following programs shows you how to use both the list techniques:
//Using RecordInsertList
static void OptimizeInsertRIL(Args _args)
{
TruckTable truckTable;
TmpTruckTable tmpTruckTable;
RecordInsertList insertList = new RecordInsertList(tmpTruckTable.TableId); //Step 1
int recordsCurrentlyInserted;
while select truckTable
{
tmpTruckTable.TruckId = truckTable.TruckId;
tmpTruckTable.TruckDimensions[1] = truckTable.TruckDimensions[1];
tmpTruckTable.TruckDimensions[2] = truckTable.TruckDimensions[2];
tmpTruckTable.TruckDimensions[3] = truckTable.TruckDimensions[3];
tmpTruckTable.TruckDimensions[4] = truckTable.TruckDimensions[4];
tmpTruckTable.Description = truckTable.Description;
recordsCurrentlyInserted = insertList.add(tmpTruckTable); //Step 2
info(strfmt("%1 records currently inserted to insertList.", recordsCurrentlyInserted));
}
recordsCurrentlyInserted = insertList.insertDatabase(); //Step 3
info(strfmt("%1 records currently inserted to TmpTruckTable.", recordsCurrentlyInserted));
while select tmpTruckTable
{
info(strFmt("%1, %2, %3", tmpTruckTable.TruckId, tmpTruckTable.Description,
tmpTruckTable.TruckDimensions[1]));
}
}
//Using RecordSortedList
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 235
static void OptimizeInsertRSL()
{
RecordSortedList recordSortedList;
TruckTable truckTable;
TmpTruckTable tmpTruckTable;
recordSortedList = new RecordSortedList(tmpTruckTable.TableId); //Step 1
recordSortedList.sortOrder(fieldNum(tmpTruckTable, TruckId)); //Step 2
while select truckTable
{
tmpTruckTable.TruckId = truckTable.TruckId;
tmpTruckTable.TruckDimensions[1] = truckTable.TruckDimensions[1];
tmpTruckTable.TruckDimensions[2] = truckTable.TruckDimensions[2];
tmpTruckTable.TruckDimensions[3] = truckTable.TruckDimensions[3];
tmpTruckTable.TruckDimensions[4] = truckTable.TruckDimensions[4];
tmpTruckTable.Description = truckTable.Description;
recordSortedList.ins(tmpTruckTable); //Step 3
}
recordSortedList.insertDatabase(); //Step 4
while select tmpTruckTable
{
info(strFmt("%1, %2, %3", tmpTruckTable.TruckId, tmpTruckTable.Description,
tmpTruckTable.TruckDimensions[1]));
}
}
If you observe both the programs, there is no much difference between these 2 programs. The
first program uses RecordInsertList class and second one uses RecordSortedList class to perform
insertion into table. Both the classes provides array insert capabilities in the kernel. This allows you to
insert more than one record into the database at a time, which reduces communication between the
application and the database. In the first program, step 1 creates the RecordInsertList object. Once the
object is created, you are ready to go for adding the records to the list. Please note how the table id is
passed and bound to the list while creating the object in the constructor. While you create the records,
add them to the list object using add() method of list and when done with adding of records, call the
insertDatabase() method to insert the records into underlying database table. While in the second
program, step 1 is used to create an object of RecordSortedList. Please note how the constructor is
called with the table id of the table that is used in sorted list. Once the object is created, you can set the
order in which the records should be sorted using sortOrder() method of the list object as shown in step
2. Create and add the records to the sorted list object using ins() method of the list object. Once you are
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 236
done with adding all the records, call insertDatabase() of the list object to insert the records into
underlying database table. This insert is also called as array inserts or bulk inserts. The major difference
between the above two approaches is that, the RecordSortedList automatically sorts the records as they
are inserted, they do not have to be inserted in sort sequence taking manual effort. In addition to the
above two approaches, you can also use insert_recordset as follows:
static void OptimizeInsert_InsertRecordSet(Args _args)
{
TruckTable truckTable;
TmpTruckTable tmpTruckTable;
insert_recordset tmpTruckTable (TruckId, TruckDimensions, Description)
select TruckId, TruckDimensions, Description
from truckTable;
while select tmpTruckTable
{
info(strFmt("%1, %2, %3", tmpTruckTable.TruckId, tmpTruckTable.Description,
tmpTruckTable.TruckDimensions[1]));
}
}
The advantage of using insert_recordset is, it copies data from one or more tables directly into
one resulting destination table on a single server trip. insert_recordset is a record set based operator
that performs operations on multiple records at a single time. This will increase performance more than
the array insert and the operation will be faster than using array insert. Compared to insert_recordset,
array inserts are more flexible if you need to handle the data before insertion is done like any complex
calculations or updates for fields are required. Though, insert_recordset is faster than array insert, the
insert_recordset is limited to simple SQL statements and expressions which may not be sufficient in all
the cases. Note how the destination fields are used and how source fields are obtained in the statement.
You can also use complex joins to fetch the data as source while working with insert_recordset to insert
the records into a table.
Note: A while select is used to display in every program is used to just display the output for better
understanding and there is no special purpose of having the statements.
Following is the comparison between RecordSortedList and RecordInsertList:
RecordSortedList RecordInsertList
Use this when you need a subset of data from
a table that is needed in sorted order
automatically while you insert into the list.
When you use this, kernel finds the
appropriate time to insert records into
database table but are inserted not later to the
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 237
This will hold only one table and a key is
defined to sort order.
You can add any number of records i.e. there
is no limit to the size but consider memory
requirements before adding very large volume
of records. Before the insertDatabase()
method can be called, list object must be
server-located or an exception is thrown.
Record level security cannot be applied and
they work faster when compared with
temporary tables.
insertDatabase() method.
Both add() and insertDatabase() methods
return the accumulated number of records
currently inserted in the list which may be
used to keep track of the number of records
added and inserted by the developer.
When non SQL tables are used or an insert()
method is overridden, the array insert will fall
back to classic one by one record insertion
RecordInsertList is very similar to
RecordSortedList with built-in support for
client/server location and it automatically
packs data from one tier to another whenever
needed which lacks the sort order.
While working with array inserts and insert_recordset there will be few cases where X++ record
set operations can fall back to record by record operations. An example is, if you override the insert()
method on the table, the RecordInsertList will do record by record insert which will not show you any
performance improvement. You have to take care of those situations to increase the performance of
your application while using these techniques.
As the above section concentrated on inserts, the current text describes how to update the
records and optimize updates of records. The following program shows you 2 ways of updating the
records in which first, using the update() method which will call update() of the table while the second
will not call update(), it is using a method called doUpdate() which is explicitly used when you need to
bypass call to update() method.
static void UpdateRecords(Args _args)
{
TruckTable truckTable;
//Update a record using update()
ttsBegin;
select forUpdate truckTable where truckTable.TruckId == "T0005";
truckTable.TruckDimensions[1] = 10;
truckTable.TruckDimensions[2] = 10;
truckTable.TruckDimensions[3] = 100;
truckTable.TruckDimensions[4] = 100;
truckTable.TruckType = TruckType::Large;
truckTable.update();
ttsCommit;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 238
//Update a record using doUpdate()
ttsBegin;
select forUpdate truckTable where truckTable.TruckId == "T0006";
truckTable.TruckDimensions[1] = 3;
truckTable.TruckDimensions[2] = 3;
truckTable.TruckDimensions[3] = 50;
truckTable.TruckDimensions[4] = 60;
truckTable.TruckType = TruckType::Small;
truckTable.doUpdate();
ttsCommit;
}
In addition to the above, you have one more statement which will update all the records at
once. This is done using a statement update_recordset. This enables you to update multiple rows in a
single trip to the server. instead of retrieving each record separately by fetching, changing, and
updating, it runs on the database server-side on an SQL-style record set which will improve the
performance with one trip to SQL. Observe the following program to understand how the
update_recordset works:
static void OptimizeUpdate_update_recordSet(Args _args)
{
TruckTable truckTable;
update_recordset truckTable
setting TruckType = TruckType::Small
where truckTable.TruckDimensions[0] <= 10;
}
Observe the above program where update_recordset is used for updating fields. This will update
all the fields in one trip to SQL that match the given criteria. You can also update the fields from another
table values. Note that this operation may fall back and work like a simple update with looping if the
update method is overridden which works like classic looping construction. As you are able to increase
the performance for inserting and updating of records, its time to look after delete operations as
follows:
static void DeleteRecords(Args _args)
{
TruckTable truckTable;
//Delete using delete()
ttsBegin;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 239
select forUpdate truckTable where truckTable.TruckId == "T0005";
truckTable.delete();
ttsCommit;
//Delete using doDelete()
ttsBegin;
select forUpdate truckTable where truckTable.TruckId == "T0006";
truckTable.doDelete();
ttsCommit;
}
The above program shows two ways to delete the record, using delete() and doDelete(). While
delete() method call the delete() method on table while deleting the records, doDelete() will bypass the
call to delete() table method. If you like to explicitly bypass the delete() of table level method, use
doDelete(). This may be required if you need to delete header without deleting the transactions due to
few requirements while working with data issues. The following program uses delete_from to delete the
records at a time. This will increase performance while you delete the records with only one trip to SQL
instead of deleting one record at a time by using the delete() method in a loop. Please note that if you
have overridden the delete method of table, this operation fall back to classic delete i.e. system
interprets the delete_from statement into code that calls the delete method one time for each row that
is deleted.
static void OptimizeDelete_delete_from(Args _args)
{
TruckTable truckTable;
delete_from truckTable
where truckTable.TruckType == TruckType::Large;
}
You may encounter a few places where you need to use few statements like ttsBegin, ttsCommit
and forUpdate keyword which are used for Transaction Tracking System which you can refer in
Transaction Tracking System.
With enough information using on queries, lets move to the next topic which will make you
understand how AX will handle resources i.e. how different files can be added directly into AX
environment and used. The topic is, resources.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 240
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 241
Resources
Resources are used to store any type of files in AX. While working with AX, you may need to
work with different types of files like images, documents and other files. Using these files storing in file
system and writing code in multiple places will be complex and risky as you may not be sure about the
file system. Instead, you can add any kind of file into the AX environment and use them. This kind of
facility is available in AX through the node Resources. Resources node is used to add the resources that
may be images, documents or files of any kind into AX and store them. AX 2012 supports maximum
integration of most of the objects with AOT Resources. In addition to the AX facility, you have few
classes which can be used to call and use the AOT Resources. Using resources, you can skip complex file
naming and avoid risk of handling file system and have all the required resources at one place.
The following steps shows how to add and use resources:
Right click on Resources node of AOT and click
on Create from File.
Browse the file system for the file and select
the file you like to add to AOT resources and
click on Open.
You can optionally set the properties Label,
Help Text and ConfigurationKey of the resource you created.
You can open and check the resource and its contents for few types of files in AX directly. Just
right click and click on open on particular resource you like to check. This will open a screen as shown in
the following image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 242
You can identify the file name and the file type from this screen. Note that only known file types
are displayed in this screen. If the file type is unknown, you may get an exception and may not see the
preview of the file content.
You can use SysResource class to access the resource node and resource node data. The
following code snippet shows how to do this:
static void UsingAOTResource(Args _args)
{
ResourceNode resourceNode;
Container imageContainer;
Image image;
resourceNode = SysResource::getResourceNode(resourceStr(AOTResourceImage_jpg)); //Step
1
resourceNode.AOTload(); //Step 2
imageContainer = SysResource::getResourceNodeData(resourceNode); //Step 3
image = new Image(imageContainer); //Step 4
}
The above code will just access the resource you created just now but will not do anything other
than that. We will see how to use resources on forms and other objects once we look the development
of forms. If you observe the code snippet, step 1 is used to reference the AOT node. You use
getResourceNode() method of SysResource class. Just need to pass the name of the resource object you
added to AOT. Once you get the reference, load that using AOTload() method. To retrieve the data from
the resource node obtained, call the method getResourceNodeData() with the reference to the resource
object. This method will return the container. In current program, you are able to see an image created
using the Image class with the data obtained from AOT resources node. Instead, you can also read text,
xml documents and other files with the data and use that using your program.
You can export and import resources also as any other object in AOT. You can see more about
export and import in Administration. Resources is a small but a topic that has good advantages. With
sufficient information on AOT resources, its time to move to the topic Forms. Forms are the way users
are going to interact with application. Please take time to go through forms with enough practice.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 243
Forms
Being developer, you will have access to any part of AOT and can perform CRUD operations
directly using table browser. You can open any node you need including the business logic in classes,
jobs etc. using AOT window. Now, consider the end users who dont know how to use AOT where they
need to perform their day-to-day business activities in AX. In addition to these, you cannot give
accessing of development environment to the end users even if they have basic development
knowledge due to security and development risk. In order to give user accessing of the process and
objects you develop and deploy, you give an advanced user interface to users. This user interface is used
for two way communication (i.e. perform all kinds of operations like Read and Write). You use Forms for
doing these operations using the UI. Forms are majorly used for following operations:
Displaying and receiving data
Filtering data
Sorting data
Modifying data
Forms are the most important objects in AX development as the end user is able to access the
entire system using the forms. You can use forms to provide validations in addition to the existing table
level validations. Application users typically use forms for all CRUD operations including sorting, filtering
and personalizing etc. This chapter focuses on forms extensively with most of the features available in
AX. This chapter focuses on AX 2009 first with a good example and will precede AX 2012 with advanced
features. Take good time to enjoy the topic with enough practice as most developers start working with
forms and use these throughout the career.
Before going to the tour on forms, AX is a complete ERP pre built system with a large volume of
features available. To support all the features, you are given thousands of forms which are used for wide
variety of applications. You can find these forms under Forms node of AOT. It is recommended not to
modify or customize the form unless and until you explicitly need customization. Just check few forms
before proceeding to get an idea or you can complete this topic and try to dig the existing forms to
understand how they are modeled. Now, its time to look into the basic forms.
Let us see the organization of forms in AX 2009 and how to create a form. You can find or create
forms in AX 2009 under Forms node of AOT. The architecture of a form in AX 2009 is very simple. Every
form will have three components as follows:
Methods
Data Sources
Designs
Methods are used to write methods on forms. Methods act like events and when a particular
operation is performed on form, the method will get triggered. You can override existing methods or
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 244
create and call new methods. Most commonly methods attached to the form are used to control the
opening and closing of the form.
Data Sources in the form are used to define the interface between the form and the database.
Data sources in form work as they work in queries and views. An entry that is referenced to a table is
added in data sources. You can have multiple data sources based on your requirement. Data sources in
the form can be used to control the relationship between data sources and other operations like create,
delete etc. In addition to these, data sources also have methods that can be overridden to control and
display of data, which will act as events on data sources.
The node Design is used to create the interface and layout of the form and its look and feel
presented to the end user. The components or objects often called as controls on the form comprise of
design and are used to present data and navigate through data on the form. Controls are divided into
different types few are used to control look and feel whereas others are used to display data. A form in
AX 2009 looks as follows. This is a form that displays the customer details:
You are able to see title bar, shortcut bar which can be used for new, save and delete operations
as well as navigation can be done using this bar. You can also find filters which can be used to filter the
records. On the right side, you can see the buttons which are used to open other related forms or
execute business process. You can observe the selected record identification is displayed in the title bar
and a selected record in the grid. Finally, in the status bar, you can see Currency, CompanyId and current
layer using. This is a most common form in AX 2009. You may find more forms in standard AX with
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 245
layout Header and Details in which header will have a grid and details section will have a grid or some
controls, header and details separated by a splitter.
Follow the below steps to create a simple form in AX 2009:
Right click on Forms in AOT > click on New > Form.
Set the property Name of the form to TruckTable. This is most commonly used
convention and best practice to give common name to table and the form that is
designed to work with master table data.
Expand the form node just created. You will find the nodes discussed above i.e.
Methods, Data Sources and Designs.
Drag and drop the table TruckTable into Data Sources node of the TruckTable form. We
dont go for methods in this example.
Now, expand the Designs node. You will find the Design. Right click on Design and add
controls. Most generally, an AX form will have a tab with at least two tab pages namely,
Overview and General. Overview will have a grid that displays most common fields
require and General will have few fields used generally. Optionally, you can add multiple
tabs other than these, but it is best practice to have these two.
To create tab, right click on Design, click on New Control, find Tab and click on that. This
will create a new control, Tab control. Set the property DataSource of the tab to
TruckTable.
Add two tab pages by right clicking Tab created in above step and click New Control >
TabPage to create tab page. Set the properties Name and Caption of the tab pages
created accordingly i.e. Overview for first one and General for second one.
Now, as per best practices, create a grid in Overview tab. To do this, right click on
Overview tab page and click on New Control > Click on Grid. This will create a grid. Drag
and drop the fields that are most commonly used to identify record from TruckTable
data source into this grid.
Now, drag and drop few fields into General tab. Based on the significance and usage of
field, you should choose what fields should be there in each tab. When you drag and
drop a field from data source on to the form, the field is created as control which is
bound to the data source. These are called as bound controls.
Create one more tab page for displaying dimensions of truck and set the properties
Name and Caption of it to Dimensions. Add the dimensions fields to that tab and give
good labels to the fields you dragged to display on the form. Please note that you can
change label of the object that should be displayed on the form. If you dont change the
label of the controls, they will inherit the label property from either EDT or table fields.
Your form looks as in the following image. We dont feel that it is very good to see the
form in its current shape and design shown due to current settings and property values.
Notice the title bar and try to resize the form. You might not be able to resize with
current property values.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 246
Now, update few properties of form to get a good look as follows:
Set the property TitleDataSource to some data source. This will make you to get the
field values in title bar of the form which can be used to quickly identify the record.
Note that, the TitleField1 and TitleField2 properties of table should be set to get these
values.
Give a good caption to the form design, which will be displayed on title bar when you
open the form.
Set the properties Width to Column width and Height to Column height of the first
control (even it might be a tab as tab is also a control). Column width and Column height
properties will enable you to update the controls accordingly on resizable forms. Set the
same properties of Grids in the form. This is used to resize the form and update the
controls when you resize accordingly.
After you set the properties as above, your form appears as shown in the image below.
Notice the title bar and try to resize the form and check how the grid and tabs are
behaving. You should find some change as per the ratio of form resized.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 247
With all the settings and complete development, your form should look as below in AOT with all
the controls. Please note that for your convenience, image is divided into 2 parts and data
source and controls are expanded and showed side by side.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 248
You can try adding more data sources and methods for the above form in AX 2009. As few
organizations are still using AX 2009 which is implemented long back, AX 2009 forms are added to this
section for reference. I request you to work on couple of AX 2009 forms to get more idea. You can check
the look and feel, design of SalesTable form for a good start on how to design more complex forms in AX
2009. With a good kick start on using AX 2009 forms, its time to get into forms of AX 2012. Please note
that except look and feel, most of the development and business logic discussed in AX 2012 forms is
applicable to AX 2009 also. You can implement the scenarios discussed in AX 2012 forms section in AX
2009 with very small changes. Lets take a look into forms of AX 2012. The following image shows how
the default look and feel of form will be in AX 2012.
In the above image, please observe the identified components namely, title bar, immediately
below title bar is the bread crumb bar which is used for navigation, and below the bread crumb bar is
the action pane which is a substitute of menus for good looking, on the left, you can find the navigation
pane which is used for quick navigation between different components in a module and below the
navigation pane is the list of modules which can be used to select the module required, in the center,
you can see the actual list page with a grid which is integrated and displayed in the grid, towards right of
the grid, the fact box which displays related information about the record, towards bottom of the grid
you can find overview pane which displays some information about the record and finally, status bar
below all the controls that can be seen which will display the info of the object like help text and the
company selected in addition to the number of alerts you received. You can set what are all the values
you like to see from Options menu. This form is a simple customer list page in AX 2012. Now that you
came to know how the look and feel of a form will be in AX 2012. AX 2012 provides you with different
kinds of forms which are used for specific purposes. The following are the different kinds of forms that
are available in AX 2012. These are also available in the form of templates in AX 2012 which can be used
to quickly design forms:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 249
ListPage: This form is used as a general form to display master table information in a quick view. It will
display only few fields and provides entry point to module. With the buttons available on this form, you
can select a record and enter into other forms or business processes using those. A sample list page is
Accounts receivable > Common > Customers > All Customers
DetailsFormMaster: This form is used to view and edit the master data. Most probably, this form is
opened when you click on a particular record and click on edit in the list page. This is the master form
that is designed for all the masters. To view a sample, you can check the form Accounts receivable >
Common > Customers > All Customers > Edit
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 250
DetailsFormTransaction: This form is used to view and edit transactional data of a master for example,
you may have multiple sales lines for one sales order. In that case, you use this form for viewing all the
sales lines for a particular sales order. You can find two divisions in the form, header and lines, where
header is used for viewing header and lines section is used for transactional data. You can check the
following form for example. Sales and marketing > Common > Sales Orders > All sales orders > Edit
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 251
SimpleListDetails: This form will have a list on left side and details on right side which is most commonly
used to have deal with reference and setup data. Whenever you select an item in list, the concerned
details are displayed in details section so that you can work with them. You can find an example of this
at Accounts Receivable> Setup > Customer posting profiles.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 252
SimpleList: This form is used to display a simple grid which can be used for basic inquiries or very simple
operations on underlying data sources. You can view a sample at Accounts Receivable> Setup >
Customers > Customer groups
TableOfContents: This kind of form is used majorly for providing some configuration parameters to
different sub set of operations to the module. You can find the example at Accounts receivable > Setup >
Accounts receivable parameters
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 253
Dialog: This is just a dialog form used for quick user interaction with very limited number of fields. You
can find an example at Product information management > Products > Products > New product
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 254
With the information about all the kinds of forms, its time to get into design of a form in AX
2012. In AX 2012, architecture of form is slightly different compared to AX 2009. You have five nodes for
any form in AX 2012 as follows:
Methods: This section is used to override the methods
or add new methods which will be called by AX
environment whenever a particular event is done. Note
that system methods act like events and you have to
call any other methods added by you explicitly from
some other method in call sequence. Most probably,
these methods are used to control startup and closing of the form.
Data sources: These are used to add the tables with which the form will connect and
work. You can add multiple data sources based on requirement. Data sources do have
method which will control the flow of data which can be overridden to control the flow
for specific requirements.
Parts: These are used to add fact boxes and other parts to the form.
Designs: This is the place where you will add the design of the form. This design is the
layout that will be displayed to end user and this is the place where the look and feel of
the form is done adding controls.
Permissions: Permissions node is used for adding security to the form. You will see more
about this in coming sections once after development of forms is done
The example below will create a list page for the tables we have, which is used to display the
ZonesTable master data. This form will be designed using the predefined template available and given
for the same purpose as follows:
Create a query with master table as data source. In previous sections you have created
ZonesTable query which can be used but should have only one data source configured
with master table i.e. ZonesTable. If any other data sources are added, you can remove
them and reuse that or duplicate the query and use that or create a new one. Here, a
duplicate of the existing is created with a name ZonesTableListPageQuery. It is not
required for you to add any ranges or order by fields.
Open the AOT > Right click on the Forms node of AOT > select New Form from template
which will display a list of available templates in forms. Click on ListPage. This will create
a list page with a name CopyOfSysBPStyle_ListPage and open in new window. Change
the name of the form immediately after you create that. It is a better practice to have
the name of the table with a suffix ListPage as the form is a list page of the particular
master table. Please note that most probably you create list pages only for master
tables. In this case, name form as ZonesTableListPage.
Set the property Query of Data Sources node of ZonesTableListPage form to
ZonesTableListPageQuery. This will be used as data source for the list page created in
this example. You can use the queries in forms as data sources as discussed in earlier
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 255
sections. Once you update the query property, the query and data sources gets attached
automatically to the form Data Sources node.
Expand the Design node in Designs node of the form. You will find three components
available for you by default. They are, ActionPane, Group Filter and a Grid. Dont delete
anything until and unless thats a hard requirement for you. Action pane will have the
default buttons you need in a list page. Filter is added for filtering the records and you
get this by default and this is the reason why you dont need any ranges in query you
create for using in list pages. Grid is used to add fields you like to display on the list
page. Set the property DataSource of Grid to the data source added in last step,
ZonesTable in this case.
Expand the data source ZonesTable under Data Sources node of the form and expand
Fields node of the data source. If that goes beyond the screen size and make you to
scroll up and down, better, open the data source in a new window and use that.
Drag and drop the fields you like to display on the list page to the grid located under the
Design node of form. It is not recommended to add system fields like dataAreaId to list
page which serve no use in list pages.
Save and try to open the form once to check how it works.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 256
Above is the forms layout you will see, if this form is opened directly from AOT. To open this
form in AX like any other other list page, you have to open this using a menu item from content pane. A
complete coverage of Menus and Menu Items will be covered extensively. For current example, follow
the below steps or read the chapter Menus and Menu Items and be back to this topic. To add menu to
content pane, follow the procedure as follows:
Expand Menu Items node of AOT. Menu items for forms are created under Display
node. Click on New Menu Item right clicking on the Display.
Set the properties of the new Menu Item as follows:
o Name: ZonesTableListPage
o Label: Zones table
o Help text: Opens zones table list page.
o Object type: Form
o Object: ZonesTableListPage
Now, select the module to add this menu item. Usually Inventory module will be
responsible for shipping and maintaining inventory. So, lets add this to common forms
section of Inventory module. To do this, expand Menus node of AOT. Drag and drop the
newly created ZonesTableListPage menu item into
InventoryAndWarehouseManagement > Common node (this looks with a folder icon).
This will create a menu item in the module. You can create this manually which may
take time and effort. For the pages that are displayed directly in content area, you
should set the property value IsDisplayedInContentArea to Yes to update the path in
bread crumb bar whenever you open this form. If you set this property value to No, you
dont find anything in bread crumb bar except the company id you are using which looks
odd.
Save and close AX client to take the changes effect. Open the AX client and select
Inventory and warehouse management module. You should find the menu item added
by you in the Common group as shown in the image.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 257
Click on the menu item you created (Zones table in above example) to see how your
form looks. It should look as follows:
With a kick start of how to create forms with a sample list page, you should add few things to
make this list page look better. The following is the current design your form looks like with the nodes
available and updated:
To update the form look and feel you should add fact boxes and update few property values.
Follow the steps to update the list page:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 258
Set the property Caption of ActionPane:ActionPaneHomeTab to Zones. You will find this
in ActionPane node of Design. This is used to display the caption of the tab page when
you open the list page.
FactBox is a small selection of data related to the current record in the listpage. There
are three types of factbox exist and can be used as follows:
o Info part: This is most commonly used type of fact box which is simple and
quickly created. With own data source in part, it can be added with controls and
limited in display, it can also be added to Enterprise Portal.
o Form part: This is just a link to a form and can have any number and any type of
controls a form can have. As this form is used as a fact box (which will have
limited space), it is recommended to consider before adding controls and using
this form as a fact box.
o Cues: This is explicitly used to show the count of the number of records related
to current record in the list page. For example, you can count the number of
trucks available for the zone and display using this type of factbox.
Follow the steps to create a factbox and attach to PreviewPane and Parts section. As
there are limited number of fields in the table, we are using the same fields in all the
factboxes and preview pane in the current example. You can try adding different sets of
fields when you try with some other table which has more number of fields.
Right click on the InfoParts node that is there in Parts node of AOT and click on New >
Info Part. Set the properties Name to ZonesTableListPageInfoPart, Caption to Zone and
Query to ZonesTableListPageQuery. You can optionally create a new query that is
required to fetch the data or can use existing one as done in the current example.
Expand the part, you will see Layout. Right click and create a new Group in the Layout
node. Set the properties Name, Caption to Zone and DataSource to ZonesTable (This is
applicable only to this example. In real case, you may need to give different values to
the properties). You can optionally set DataGroup property which will have field groups
of table referenced by DataSource which will display the list of fields that are there in
DataSource. In this example, we will add the fields manually.
To add a field manually, right click on the group, click on New Field. Set the properties
values of Name to Id, Label to Id, DataSource to ZonesTable and DataField to
ZoneId of newly created field in the current scenario.
Follow the above step and create two more fields namely, Description and ZoneType
and set the properties.
Once fields are added, click on Save and create a display menu item for this info part.
You can create a display menu item directly by drag and drop the info part on to the
Display node of Menu Items which will save your time and effort.
Drag and drop the display menu item created in last step on to the node Parts of the
form. Whenever a user select the record, the factbox should reflect the changes and
update the data in it. To do this, there should be a relation existing between factbox and
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 259
the list page. You can specify the property DataSourceRelation of the factbox created,
which is most commonly SelfLink.
Create one more info part and attach to PreviewPane. There will be only one preview
pane to one list page and can have multiple factboxes. The only difference that variants
factbox and PreviewPane is, the property PartLocation which will specify if it is a
PreviewPane or a factbox. Update the MenuItemName property of PreviewPane to
attach to info part which will update that PreviewPane to use info part for displaying
related information.
As the new button has an odd image, add an image to resources as discussed in
Resources section, find the NewButton in ActionPane and , set the values of properties
ButtonDisplay to Text & Image Above, ImageLocation to AOTResource and
NormalImage to the name of the resource you added in resources node. This will display
the image on button instead of default image as shown in the below image.
You can check how your form is displayed in the following image:
Note that you cannot override the methods in list pages. Instead, you have to write
Interaction classes to handle the events and other actions of list pages.
ListPageInteraction classes are used to interact with the list pages when an action is
done. This class will have event methods that act according to the selection of the
record in list page which can be used to call other methods you create. The most
common scenario to use list page interaction is, you may need to enable and disable
certain controls on the list page based on the record selected. For example, while
working with sales orders list page, if you select an open sales order, the button Invoice
should be enabled whereas, it should get disabled once you select an invoiced sales
order. The same scenario is simulated using list page interaction class in current
example. Scenario is, if you select a Zone with zone type being Big, New button should
be enabled otherwise, disabled.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 260
Implement a list page interaction class for this scenario as follows:
public class ZonesTableListPageInteraction extends ListPageInteraction
{
ZonesTable zonesTable;
}
/// <summary>
/// Calls the enableButtons Method to enable or disable the buttons upon record selection on the
grid.
/// </summary>
/// <remarks>
/// Method is called by the framework each time a record is selected in the grid on the list page.
/// </remarks>
public void selectionChanged()
{
super();
zonesTable =
ZonesTable::findRecId(this.listPage().activeRecord(queryDataSourceStr(ZonesTableListPageQuer
y, ZonesTable)).RecId);
this.enableButtons();
}
/// <summary>
/// This method will enable or disable a method based on selection of record.
/// The criteria for enable is, if ZoneType is big, the button will be enabled or disabled.
/// </summary>
/// <remarks>
/// This method is called by selectionChanged method.
/// </remarks>
public void enableButtons()
{
if(zonesTable.ZoneType == ZoneType::Big)
{
this.listPage().actionPaneControlEnabled(formControlStr(ZonesTableListPage, NewButton),
true);
}
else
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 261
this.listPage().actionPaneControlEnabled(formControlStr(ZonesTableListPage, NewButton),
false);
}
}
Please note that /// are used for documentation comments and it is a better practice to
write the document comments to the methods you write which will help you later when
you open the method.
Observe the declaration of class done, class ZonesTableListPageInteraction extends
ListPageInteraction which extends ListPageInteraction. This will make the class get the
default behavior that can be used for list page interaction. It is better practice to follow
naming convention for identifying the object easily further.
The most important method in the class which can be overridden is, public void
selectionChanged(). This method is called automatically whenever the selection of
record is changed on the list page i.e. when a user selects different record, this method
is triggered. You can write few more methods as done in the current example, public
void enableButtons() which can be called from selectionChanged().
You can access the ListPage object using this.listPage() which can be used for multiple
purposes like identifying record that is selected by user or finding the controls and
working with them etc. as did in enableButtons() method.
Once you are done with interaction class, you can set the property InteractionClass of
list page form you created i.e. ZonesTableListPage to ZonesTableListPageInteraction and
save.
Open the list page form and check the working of your interaction class based on your
selection. Your final form will display as shown in the below image with a disabled New
Button.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 262
Check clicking on New, Edit or other button and find how they works. The buttons New
and Edit will throw an error No object specified on menu item as you dont have any
master form whose menu item is set to New or Edit buttons. To do this, first, you have
to create a master form (DetailsFormMaster) and update the menus New and Edit
which will reference that master form.
Follow the below steps to create a master form for ZonesTable:
Right click on Forms node in AOT and click on New Form from template >
DetailsFormMaster. You will see a new window opened with a new form created having
name CopyOfSysBPStyle_MasterDetails. Update value of property Name to ZonesTable.
As a best practice it is recommended to name master form with the name of table name
for easy identification.
Drag and drop the table ZonesTable into the Data Sources node of the form created in
last step.
Following is the structure of nodes defined for any Master form in AOT.
This is action pane, you will find all the tabs and
buttons and menus etc. in each tab here.
This is the header tab, header name and
optionally other header related stuff will sit here.
This is the general tab of that display header
information.
This is the Grid that is displayed when grid view is
selected by user.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 263
With the information from above image, please find the navigation path accordingly. Set
the value of property Text of HeaderTitle control to Zones. This is used to display the
title on the header tab in form.
Expand the data source, drag and drop the fields ZoneId, ZoneType and Description onto
TabPage:HeaderGeneral tab. This will create controls in your tab and you can find them
immediately. You can optionally change the labels of the controls created. Change the
property value of Label of Description to Name. You can find other properties like,
Visible, AllowEdit, Font, ConfigurationKey, Mandatory etc. which can be used based on
your requirement. You can take a look on available properties and possible values.
If you have large number of fields in your table, you can add multiple tabs and add the
fields to each tab accordingly. As there are very few fields in ZonesTable, we are
restricting to only one tab here.
Drag and drop few (probably, same as header) fields into the Grid to display the user
when grid view is selected. Set the value of property DataSource of Grid to ZonesTable.
Update the value of Caption property of ActionPaneTab:HeaderHomeTab to Zones.
When you create new tabs n ActionPane, it is best practice to set the field values
accordingly.
To get better look, set the properties of Design as follows:
o Set the TitleField1 and TitleField2 properties of table referenced through data
source. It is best practice to set the TitleField1 and TitleField2 of the table when
you create the table and add the fields.
o Set TitleDatasource and Caption properties of Design node of the form.
Save and open the form. Your form should look like this. This is details view. Please
check the next image for grid view:
Action pane
Header tab
View change
buttons.
Navigation
buttons.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 264
Try editing and deleting the record, you will be able to see them working. Now create a
menu item of type display for this form, just drag and drop the form to Display node
under Menu Items node and set properties.
Set the property of New Button in your list page to the menu item of master page. This
will make button working and when you click on the button, it will call the master form
with blank fields which allows you to create a new record. Check the image for the
property values:
Check opening your list page and click on New button which will open your Master page.
This is how you link the master pages with your list pages. Now, add few more
properties of list page as follows:
Similarly, set the property MenuItemName of EditButton, ViewButton and
EditInGridButton to appropriate Menu Item, usually, ZonesTableMaster (in this case).
Grid in grid view.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 265
Master is also added factboxes as a usual practice to display some extra related
information on the form. You can add the same fact boxes or create a new set of
factboxes and add them to the form.
When you click on New button on the master page, you will get an error in current form.
You should update the property MenuItemName of the new button in master page, but
whenever you click on that, it will open another similar form to create record. Instead,
you can add a command button and set the Command property to New which will not
open new form every time, instead, you will see blank record/controls to enter values in
the same form. Finally, your form should look as shown below:
Now, create the list page and master form for TruckTable as exercise. As there are
multiple fields, add few tabs and use field groups instead of adding fields individually to
the tab. To do this, create field groups in table accordingly as per significance of fields
and add the field group in the tab. Your TruckTableMaster should look like this:
Fact boxes
Image displayed on
New button updated
with AOT Resource.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 266
Fast tabs are nothing but the tab pages added to your header Tab. These are alternative
to regular tabs where you can see multiple tab pages opened at a time. There is an
interesting property, FastTabExpanded which will specify whether the fast tab should be
expanded or not when the form is opened. The following image shows you the tab
pages created in the design of the form as follows.
Just check design, you have 3 tab pages which can be seen in the form. Right click on
Tab and add the number of tab pages you need which will work like fast tabs. This is a
new feature in forms in the version of AX 2012.
Fast tabs.
Three tab
pages created.
Property
FastTabExpanded.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 267
Opening one form from another form is very easy in AX 2012. You have to simply add
the menu item button on the form from where you like open other form. Usually, this is
done only for the related tables. In the current case, you can try adding a link to
ZonesTableMaster form which should open the related zone for the truck record which
can be used for editing or other operations. Check the below image on how to add the
button group and buttons.
First add a button group and add a menu item button and set the properties or drag and
drop the menu item ZonesTableMaster to this group which will reduce the time and
effort you need to spend for creating and setting the properties of the button.
Optionally, you can set the DataSource and other properties of the button to pass
parameters to the called form if required. Now, your form action pane should look as in
the below image:
Click on that and open the form to check how the stuff works. When you click on this
the matching Zone record should get opened in the Zones master form.
With enough information about working with list pages and master pages, its time to see
couple of other form layouts with examples, DetailsFormTransaction and SimpleList. The first one is
used to display header and transactions whereas the second one is used to display a simple list in a grid.
Follow the below steps to create DetailsFormTransaction: [This topic also covers Joins on data sources]
Create a form from template of type DetailsFormTransaction and rename that to
ZonesTruckDetails.
Add two data sources, ZonesTable and TruckTable to the form. Set the property
JoinSource and LinkType of TruckTable data source to ZonesTable and Delayed. The
following is the significance of this step:
o JoinSource is used to set the join between two data sources on form. When you
change the header data source, this will update the child data source
accordingly based on the LinkType you select. LinkType enables you to maintain
Menu item to reference
another form.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 268
an active link between two data sources. When focus changes in the first data
source, the corresponding records in the other data source are selected with
this LinkType.
o The LinkType have 7 values which will have Passive, Delayed, and Active, which
will be used when you join data sources that have a parent/child relationship,
and InnerJoin, OuterJoin, ExistJoin, and NotExistJoin should be used when
joining data sources to appear as one data source and works similar to joins in
queries. Following statements explains about the different LinkTypes' except
joins.
o When Passive is used, the linked child data sources are not updated
automatically. Updates of the child data source must be programmed on the
active() method of the master data source.
o When Delayed is used, a pause is inserted before linked child data sources are
updated. This enables faster navigation in the parent data source because the
records from child data sources are not updated immediately. For example, the
user could be scrolling several zones without immediately seeing each truck
lines available for each zone.
o When Active is used, child data source is updated immediately when a new
record in the parent data source is selected. Continuous updates consume lots
of resources and time which is not required in all cases.
The most commonly used LinkType is Delayed, where a small latency is taken before
updating the child/linked datasource. This will not make any difference for an end user
while scrolling through multiple records and it will not take much time to update and
fetch the child data source.
Any DetailsFormTransaction form will have a header view and lines view. In addition to
that you will have details view and grid view. You should either configure all of these or
disable few features. You can find the buttons as in the below image:

Header view will display only the header information and line view display the header
information and concerned lines. As you have seen in the above setting, the concerned
lines are obtained using JoinSource and LinkType properties of the child data source.
You can have multiple data sources which can be connected and the data from all the
data sources will be displayed in different controls, connected with data sources when
you select Line view. You will get Header view and Line view buttons by default when
you select the template and the design will be given accordingly.
Header view Line view
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 269
Your form design looks as in the below image where you can find tabs and various
controls given for different views available.
The below statements explain you about each component identified:
o When you click on Header view button in ActionPane, you will see only the
content of HeaderView tab page. Others will be invisible.
o When you click on Line view button in ActionPane, you will see the contents of
LineView tab page and others will remain invisible.
o When you select the details view, you will be able to see TabPageDetails and the
TabPageGrid is viewed when you click on grid view of the form. You will find
these two buttons on status bar of form.
Details view tab.
Grid view tab.
Header view tab.
Line view tab.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 270
First update the Grid view, drag and drop the most common fields from ZonesTable data
source to HeaderGrid under TabPageGrid and set the value of property DataSource to
ZonesTable. Now, your grid view is ready. You can open the form and check grid view
how it looks. The below image shows how design looks after you add the fields to the
grid from data source.
Set the property Text of control HeaderTitle in
HeaderInfo group of TabPageDetails tab page to
Zone. This will show the header title. You can
see the navigation path in adjacent image.
Now, update HeaderView first which is simple to configure. Just add the required fields
from parent data source i.e.
ZonesTable data source to the
HeaderGeneral tab page in
HeaderDetailsTab of HeaderView. You
can add few more tab pages and add
optional fields or data to this section if
you have multiple fields as discussed in
previous sections. This will show the
fields populated with the values when
you select the header view. You can
find the design after adding the fields
to the header view which looks as in the adjacent image.
To add new tab pages, right click on HeaderDetailsTab and click on New Control >
TabPage. This can be used to add new tab pages to your tab. It is not required to add
new tab pages now as per the requirement.
Open the form and test the development you have done till now, you should be able to
see the grid view and header view populated with values.
Now, coming to lines view, you have to
configure for tab page LineViewHeader,
LineViewLines and LineViewLineDetails. You
will find all these under node tab page
LineView.
First configure LineViewHeader. Check the
adjacent image. Set the caption of the tab
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 271
page LineViewHeader to Zone and drag and drop the few fields from ZonesTable data
source to this tab page.
Update property Caption
of tab page
LineViewLines to
Trucks to display
appropriate header to
tab page.
Add required fields from
child data source i.e.
TruckTable to Grid in
LineViewLines. Set the
DataSource property of
grid to TruckTable as
TruckTable represent
the child in the current
case.
Set the property Label of
TruckDimensions[1],
TruckDimensions[2],
TruckDimensions[3], TruckDimensions[4] to "Length", "Breadth", "Volume" and
"Capacity". All those are controls in grid. Please note that once you drag and drop fields
from the data source to design, they will act as control.
Configure the DataSource property of LinesRecordActions button group in
LinesActionPaneTab to TruckTable. This is a required step because, when you click on
New and Remove buttons, action should be done on TruckTable (child data source
configured for lines) data source and if you dont configure this, you may end up with
deleting record of other data source. Once you configure these, you should be able to
see the design as seen in image above.
Open your form to check the progress and
you feel the design. Finally, plan the
LineViewLineDetails tab page. As you have
different sub sets of data in the
TruckTable, have three tab pages namely,
General, Zone info, Dimensions. You will
find General by default. Create two more
tab pages and set the Name and Caption accordingly. You should see the design as in
the adjacent image.
Now, add the fields accordingly to each of the tab pages. General info like TruckId and
TruckType will sit in General. ZoneId and ZoneType will sit in ZoneInfo tab page and
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 272
Dimensions will sit in Dimensions tab. With the development done, your design should
look as shown in below image.
Create a factbox as shown in the image and add that to
this form in parts section. Please read the above
discussed process to create a fact box. The fact box here
will display multiple lines of trucks available under the
selected Zone. To do this, you have to set the value of
property Repeating of group you created in Info Part to
yes. The query used for this factbox should have 2 data
sources, ZonesTable and TruckTable connected using inner join to fetch multiple related
records combined as one data source.
Create a display menu item for this factbox and
add it to parts node of form and set the property
DataSourceRelation to SelfLink to form the
relation. Your form final design from outer view
look like as shown in adjacent image.
Set the TitleDatasource and Caption properties of
the design of the form to ZonesTable and Zones
accordingly and check for the TitleField1 and
TitleField2 of table. Update the MenuItemName
property of the buttons in the action pane for
required buttons or they will not work. Finally,
update value of caption property the tab HeaderHomeTab in ActionPane to Zone or
you will see an odd tab name.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 273
Save and compile your form once to check if there are any errors and create a display
menu item for the form you just created which can be added to a module in Menus. You
can add this to the Inventory module below the Zones menu item.
Now, open your form to see the look and feel of your form. It should look as shown in
the below image.
You can optionally try updating the properties of few other buttons and objects to find
how your development and modification works and reflect the environment.
With the above exercise, you know how to develop more complex forms. Note that the data
source of form is treated as query and can be accessed in code if you have any requirement. You will see
objects to access the query of the data source in coming text. You can study the design of couple of
forms namely, CustTable and SalesTable where you may find some complex design you can understand.
Now, add the Zones button to the above form and try opening Zones form from the above form with the
newly added button. You can see the form getting opened but will not reference the actual Zone and
may display some other Zone or probably first zone record. To get the correct record, you have to filter
the records in the form you open, getting the required record. To do this, you have to write code in the
form explicitly for filtering operation and its time to understand how methods are handled in forms.
Though there are lots of powerful tools available in AX for development and working with forms,
you may need some explicit control over the development environment. This is possible only through
code. For example, consider a requirement where you like to validate the form data based on some
condition which is specific to form. In that case, you cannot do validation at table level as if you write
code at table level, this will reflect everywhere. Consider one more scenario you have to open a sales
invoice form. In this case, you have to check whether a sales record is passed or not and throw error if
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 274
there is no sales record passed, or accept and invoice the same. In that case, you have to write code to
check when the form gets opened. To do these, you have few methods available in forms and form data
sources, You can expand the form node and data sources node to find them. In addition to forms,
method can be overridden for data sources, controls also. There are a variety of methods available
which will be called for different events on forms but only few are used very frequently. Lets have look
at most common methods that are used on forms, data sources and controls. Following are the most
common used while working with forms:
Form methods
init() This method is called whenever the form is opened. This method
will initialize the form from super () call and is called only once
when form opens. You can override this method to perform any
initialization tasks on the form. Never comment/remove super ()
in this method.
close() This method called before the form gets closed. This method if
used to finalize or close any external resources opened in form
while initialization is being done. This method shuts down the
form.
closedOk() This method is called when an OK button on the form is called
for which the Type is set to OK. super() in this method will close
the form. This can be used to validate the data before form is
closed.
canClose() This method is called before close() is called. This method will
return a boolean value based on which the close() is called. This
can be used to do any final validations on the form.
run() This method is called after init() is executed. The super() call
makes the form window appear on the screen. This method is
used to initialize controls and while working with third party
controls to register events etc.
Data source methods
active() This method is called when a record becomes active in the data
source. This method is overridden to change properties which
depend on the contents of the current record, which is most
probably used to update permissions to the fields, or
enable/disable buttons or controls.
linkActive() This method joins data sources. This method is called on the
joined data source every time that the active record in the main
data source is changed. The method is also called when the form
is opened as the system tries to join the calling data source to
the main data source of the called form.
delete() This is called when a record is deleted on the form of particular
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 275
data source. This will call the underlying table method from
super() call.
executeQuery() This is executed when the data, or subset of data, of the data
source is to be retrieved. You can use this method to update the
query in order to fetch only a particular sub set of data based on
few conditions, like adding form control based filters.
init() The super () in this method will initialize the data source of the
form. You can use this to initialize the ranges etc. and the super
() in this will initialize the data source. Never remove or
comment super () in this method which may lead to unexpected
results.
initValue() This method is called whenever you create a new record on the
form for that data source. This will call the underlying table
method from super () and is used to initialize form specific
initializations. This call will override the initializations done at
table level.
write() This method controls the insert and update of records. This
works similar to table method and the super() of this method
calls the corresponding method on the underlying table. If you
have any form specific tasks to perform in relation to the record,
you can add it here.
validateWrite() This method validates an insert or update of a record. This works
similar to validateWrite () of table. The super () of this method
calls the corresponding method on the underlying table.
validateDelete() This is similar to validateDelete () of table. This will check
whether the record can be deleted or not. The form level
validations can be added in this method. This method will have a
super() which will call the underlying table method.
Data source Fields and Controls methods
lookup() [Data source field] This method is called when a lookup is being performed on the
control. You can override this method to override and display
custom lookup.
modified()[Data source field] This method is called when the field value is modified. This will
call the underlying table method modifiedField () using super ().
Use this if there are form specific modifications are to be done.
validate()[Data source field] This method is called when the field value is modified and should
be validated. This method returns boolean value, which if
returned true will proceed to modified () otherwise, will throw
error. This will call the underlying table method validateField ()
using super (). Use this if there are form specific validations to be
done at datasource field level.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 276
lookup() [Form control] This method is called when a lookup is being performed on the
control. You can override this method to override and display
custom lookup.
modified()[Form control] This method is called when the field value is modified. Use this if
there are control related modifications are to be done.
validate()[Form control] This method is called when the field value is modified in control
and should be validated. This method returns boolean value
which if return true will proceed to next step otherwise, will
throw error. Use this if there are control specific validations to
be done at field level.
selectionChange()[Form control] This method is called on combo when a value selection is
changed. You can use this to reflect other controls or records
when the selection of this control value is changed.
clicked() [Form control] This method is called when a mouse click is done on the controls
like button etc.
In addition to the above discussed methods, there are many methods which are not discussed
and may not be used more commonly. With an introduction of methods, its time to write a method
considering the scenario. Whenever you create a truck, the ZoneId and ZoneType should be defaulted to
those that are selected on the form. To do this, there should be some code snippet which will assign the
values to those fields. The following example demonstrates that.
Override the initValue () of TruckTable data source in ZonesTruckDetails form created in last
exercise as follows:
public void initValue ()
{
super ();
TruckTable.ZoneId = ZonesTable.ZoneId;
TruckTable.ZoneType = ZonesTable.ZoneType;
}
In addition to the assignment, add a validation for volume of the truck where if the zone type is
big, truck should be large. To do this, you have to override the validateWrite () of TruckTable data source
as follows:
public boolean validateWrite ()
{
boolean ret;
ret = super ();
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 277
if (ZonesTable.ZoneType == ZoneType::Big)
{
if(TruckTable.TruckType != TruckType::Large)
{
ret = ret && checkFailed ("Invalid combination of ZoneType and TruckType.");
}
}
return ret;
}
Consider a scenario where you dont like to allow modification of zone type of truck when the
selected TruckTable record has a value Large for truck type. There is no specific reason except that the
business scenario demand due to a fact that Large trucks are meant for big zones and this is taken care
while a record is inserted into table. To do this, you have to disable the form field so that there will be
no chance for user to edit the field and save the record. Whenever a record becomes active, the method
called is active (). Override active () method of TruckTable data source as follows:
public int active()
{
int ret;
ret = super();
if(TruckTable.TruckType == TruckType::Large)
{
truckTable_ds.object (fieldnum (TruckTable, ZoneType)).allowEdit (false);
}
else
{
truckTable_ds.object (fieldnum (TruckTable, ZoneType)).allowEdit (true);
}
return ret;
}
Note how the field is disabled. While working with form and data source methods, you need
knowledge of all the inbuilt objects provided by X++ to access the data sources, forms etc.. Following are
the objects provided to access different objects on a form while working with methods:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 278
Object Access from X++
Form Element
Data source of in the form <name of data source>_ds
Current record selected <name of data source>
Design of form Element.design()
Control on form Directly with name if AutoDeclaration property is set to Yes
Or
element.control(Control::<name of control>);
Query of form <name of data source>_Q
QueryRun of form <name of data source>_QR
Field in data source <name of data source>_ds.object(<fieldId>);
You can access various objects in the methods of form using the above objects. In the similar
way, the statement truckTable_ds.object (fieldnum (TruckTable, ZoneType)).allowEdit (false); is used
to get the field and disable that field.
Now, consider the requirement when you added the menu button to open ZonesTableMaster
form, from the ZonesTruckDetails form. You can open the ZonesTableMaster form which is not showing
exact record that is selected in the ZonesTruckDetails from as selected. Now, lets write code to update
the record in zones master form if the caller is ZonesTruckDetails. To do this, follow the below steps:
Step 1: Declare variables in class declaration, as they will be available in all the methods
public class FormRun extends ObjectRun
{
QueryBuildRange qbrZoneId;
ZoneId zoneId;
}
Step 2: Add the following code which is a range in the init () method of ZonesTable data sources
public void init()
{
ZonesTable callerRecord;
super ();
if(element.args ().caller () && (element.args ().caller () is FormRun))
{
If(element.args ().caller ().name () == formStr(ZonesTruckDetails))
{
callerRecord = element.args ().record ();
ZoneId = callerRecord.zoneid;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 279
qbrZoneId =
ZonesTable_Q.dataSourceName(tableStr(ZonesTable)).addRange(fieldNum(ZonesTable,
ZoneId));
}
}
}
Step 3: Add the range value in executeQuery () method
public void executeQuery()
{
If(qbrZoneId)
{
qbrZoneId.value(SysQuery::value(zoneId));
}
super();
}
Understand the flow of the code that will be executed. When you open a form, init () of data
source gets executed first and later executeQuery () is called. So, the initialization like adding range to
query is done after the data source is initialized as you can see, the code is after super () call. As said
earlier, please note that data source in the form is just a query which fetches the records and display on
the form. If you observe the executeQuery (), the code is written before super () as super () will execute
query and adding range value after execution will be of no use. So the range is added before execution
of query i.e. before super (). For time being, just understand that args() is used to pass arguments from
one object to another object. Args is covered extensively in this chapter. You can refer to Args first and
later come to this topic if you feel confusing.
Now, there is one more scenario where they dont like to delete the trucks record if truck type is
small currently. If you write the code in table method, it will reflect in all the calls, from any
part/code/form of AX you try to delete the record. This may not be feasible and, we need to write the
code in form level method for the same reason to avoid being called throughout AX. You can write the
code in validateDelete () of data source TruckTable of the ZonesTruckDetails form to validate the
deletion of the record as follows:
public boolean validateDelete ()
{
boolean ret;
ret = super ();
if (TruckTable.TruckType == TruckType::Small)
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 280
{
ret = ret && checkFailed ("Deletion of Trucks of type small is temporarily suspended");
}
return ret;
}
Few times, you need to open few forms only if they are called from some caller. If not, you have
to throw error. In that case, just check for the caller as shown below. Usually, you check for caller when
a form is opened, and the method that gets executed when a form is opened is init (). Write the
following code in init () method of ZonesTableMaster:
public void init()
{
if (! element.args ().caller ())
{
throw error ("Form should be opened by some caller.");
}
super ();
}
As you have seen various methods in forms, data sources and you have methods even at
controls level, the methods are called in a sequential manner. This will enable you to write methods that
should be executed in an organized way and these will also facilitate you to place the code at best
position or level to get best output. With enough workouts on form methods, lets try to understand the
sequence of methods executed to find the best place where the code can be written as follows:
1. Following sequence is executed in lift time of a form:
init () of form is called
init () of data sources from init of form i.e. super() will initiate the call
run () of form from super() of init() of form
executeQuery () of data sources from run() of form
refresh () of data source
active () of data source
2. Following sequence of methods is called when you close the form:
canClose() of form
close() of form
3. Following sequence is executed for each data source when the form is opened:
init() of data source
executeQuery() of data source
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 281
4. Following sequence is executed when you refresh form:
executeQuery() of data source
active() of data source
5. Following sequence if executed when you create a new record:
create() of data source
initValue() of data source from super() of create()
initValue() of table from super() of initValue() of data source
active() of data source
6. Following sequence is executed when you modify a field:
validate() of field in data source
validateField() of table from super() of validate() of field
modified() of field in data source
modifiedField() of table from super() of modified() of field
7. Following sequence is executed when you save a record:
validateWrite() of data source
validateWrite() of table from super() of validateWrite() of data source
write() of data source
insert() of table from super() of data source or update() of table from super() of data
source
Note that, when you create a new record, insert() of table is called and when you edit
and update an existing record, update() of table is called. AX will take care of the
method calling.
8. Following sequence is executed when you delete a record:
delete() of data source
validateDelete() of data source from super() of data source
validateDelete() of table from super() of data source
delete() of table from super() of data source delete()
Need of methods at different levels: You may wonder why methods are required in these many
places and these many levels. You have to understand why these are required at different levels.
Consider the case where you need to write validations that should work when you try to do operation
on table from anywhere. Please note that anywhere stands not only from any form, this may be implied
to Enterprise Portal where a web page may access and do the database operation or a web call by a web
service may do the same operation. In that case, you write the validation at table level. This is triggered
regardless of the call from where it is done. Consider a case where you may need simple validations for
specific requirement only. In that case, you go for form level validations. Please note that it is better
practice to write simple validations in form level in order to avoid server round trips which will be costly
process. You may need to do some complex operation which will have large volume of code. In that
case, it is recommended to write the code in class and call from the place you need. In addition to this,
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 282
you can write code at some other places where you have methods in Queries etc. to choose the best
place, the only method is analyze the requirement and write code at the perfect place. You can write the
code for a requirement in multiple places which may involve performance cost which will impact a lot in
some cases.
With enough information on forms, data sources and methods its time to understand how the
data source can be controlled. Following are few interesting properties of form data source and data
source fields which can be used to control the data and its flow on the form:
Data source properties
AllowEdit This property is used to restrict or allow editing of the record on the form
referenced by particular data source. You can enable or disable a data source
editing from X++ code also. Note that editing can be controlled on individual
fields also. These are in addition to the user level security.
AllowCreate This property is used to restrict or allow creation of record on the form
referenced by particular data source. These are in addition to the user level
security.
AllowDelete This property is used to restrict or allow deletion of the record on the form
referenced by particular data source. These are in addition to the user level
security.
JoinSource This is used to specify the data source to which the join should be made.
LinkType This will determine how two data sources are joined. Please refer to join
sources section discussed in above sections.
InsertAtEnd This property is used to specify whether or not a form should automatically
create a record if the user browses past the last record. The event is similar to
creation of new record by user and will save only if the user change or modify
the field values.
InsertIfEmpty This property is used to specify whether or not a form should automatically
create a record if there are no records in data source, or form dont find any
data i.e. record set is empty. The event is similar to creation of new record by
user and will save only if the user change or modify the field values.
Data source field properties
AllowEdit This property specifies whether the value of the field can be changed or not on
the form.
Enabled This property will enable or disable the control represented by field on the
form. If the field is disabled, it is grayed out and the field cannot be accessed by
user.
Visible This property specifies whether or not the control represented by field should
be visible.
Mandatory This property specifies whether or not the field value is mandatory. You can
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 283
specify the field as mandatory if a value should be entered into the control
represented by field and the control will throw error if you try to save the
record without giving a value. Note that if the field is not mandatory in table
level also, you can make the field as mandatory in data source level.
Lets create one more form using the template SimpleList. This is a very simple form used to
display the list of Zones in the grid.
Create a form from template of type SimpleList and name it as ZonesList.
Drag and drop the ZonesTable to the data sources node of the form.
Expand the Design node where you will see three child nodes namely PageTitleGroup,
CustomFilterGroup and GridContainer.
Drag and drop ZoneId, Description and ZoneType from ZonesTable data source to the
grid in GridContainer. Set the value of property DataSource of grid to ZonesTable.
Set the value for property Caption of Design to Zones and TitleDatasource to
ZonesTable.
Open the form and see how the look and feel is.
You can set properties of few other objects or controls like the New button etc.
Now, create a ComboBox control in the group CustomFilterGroup by right clicking on
group and selecting New Control > ComboBox
Set the properties values of Name to ZoneTypeCombo and AutoDeclaration to Yes
and EnumType to ZoneType. This control is called as Unbound control. We will see the
different types of controls available in forms in coming text.
Set the value of property Visible of the group CustomFilterGroup to Yes.
Open the form and you should see the form with the custom filter you created. Your
form should look like the following image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 284
The requirement is, you have to filter the records based on the filter you select. To do
this, you should add range and filter the records based on the selection of the combo.
The following code does this:
public class FormRun extends ObjectRun
{
SysFormSplitter_X verticalSplitter;
QueryBuildRange qbrZoneType;
}
public void init()//This is init() of data source ZonesTable
{
super ();
qbrZoneType = ZonesTable_Q.dataSourceNo (1).addRange (fieldNum (ZonesTable,
ZoneType));
}
public void executeQuery ()//This is executeQuery () of data source ZonesTable
{
qbrZoneType.value (SysQuery::value (ZoneTypeCombo.valueStr ()));
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 285
super ();
}
public int selectionChange ()//This is selectionChange () of ComboBox ZoneTypeCombo
{
int ret;
ret = super ();
ZonesTable_ds.executeQuery ();
return ret;
}
The above example shows you how to add custom filters. Just add the range and update
the range whenever the selection of combo changes, and call executeQuery (). This call
is required because the records should be fetched based on new range value of the
query.
Note the event method selectionChange () of combo box. This method is triggered
whenever the value is changed in combo. To get the value from combo, valueStr ()
method of combo box is used, and to provide accessing of the combo using the name
directly in any code part, value of property AutoDeclaration of the combo is set to Yes.
Bound and Unbound controls
When you add controls to the form or to the tabs or groups in the form, you can add them in
one of the two ways. First one is, add the controls directly right clicking on the Design or Tab Page or
Group or any other container or drag the field from data source and drop on the container. If you drag
and drop the field of data source, this field will
be bound to that data source and display the
data of the column of current record
referenced by that data source. If you check
the properties of the controls, you will find two
interesting properties, DataSource and DataField. These two properties of a control will make that a
bound control if they are set to some data source and note that, if you do any operation on this control,
it will reflect in the underlying database table. You can find the properties in the image.
When you check the same for normal controls created by you, these properties will be empty
and whatever the data you change in those controls will not reflect in the database table and you dont
find any connection to the control with the data source. Please note that you can set these properties to
the control which make the control a bound control. Unbound controls are of a specific type and are not
attached directly to a table. These controls may optionally inherit the properties of the data type
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 286
including any relations to a table based on requirements to get a lookup etc. An unbound control can be
used for filtering record to be displayed or to display images or static text. etc.
So, the difference between bound and unbound controls is just very simple, Bound controls are
associated with a field in an underlying table and you use bound controls to display, enter, and update
values from fields in the database, whereas, unbound controls do not have a data source, but optionally
can be a specific Extended Data Types or Enums.
In the above example, you have used an unbound control, the combo box named
ZoneTypeCombo. In addition to this control, you have a large number of controls available which can be
used for different purposes. The following table explains about the controls available and will be
discussed in an example with the events available for each of the control after completion of the table:
Control Purpose Event
ActionPane This control is used to add the action pane bar to the
form. You can add multiple action pane bars like you
did for ZonesTruckDetails form.
ActionPaneTab This control is added to the action pane used to
group the related actions into a group and label
them. The actions are most commonly buttons or
button groups.
ActiveX These are deprecated and ManagedHost is used
instead of this control. This control is used to add any
ActiveX control available on the computer into your
form.
Animate This control is used to play an avi file. You have few
properties to set the properties like infinite looping
etc. This is most commonly used to show user an
experience of copying of records etc. activities.
Button This is a regular button control. You have different
types of button controls in AX. You have to write your
own code to make this control work.
clicked () is the method
you override to set the
behavior of this button.
ButtonGroup This is used to group buttons together which belong
to one group and name them. You can use this to
group all the related actions and label.
CheckBox This is a box used to select or clear state of an option
usually, visible as checked or empty. You can select
multiple options if multiple are available. checked ()
method can be used to find the state of the check
box which will return a boolean value.
modified () method is
triggered when you do
check or uncheck action.
ComboBox This will display a list of options in which one chosen.
Most commonly you are not provided an option to
selectionChange () is the
event method triggered
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 287
type and you select from the available except in
exceptional cases. You can attach this to an enum
also which will display the list of values in enum as
did in last example. valueStr () is used to get the
current selected value in this control.
when you change the
selected value.
CommandButton When you do most common action like Close a form
or create a new record, you can use these buttons
attached with an action instead of writing. This
button will have a property called Command which is
used for setting the action that will be done when
this button is clicked.
DateEdit This is the control used to display and edit dates. You
can use the method dateValue () to get the current
date value given in this control.
modified () is the method
triggered whenever you
update the value in this
control.
DropDialogButton You can use this button to open a small form which
can be used to update specific values. You use the
MenuItemName property to specify the menu item
and the form referenced by the menu item is
displayed.
Grid This is used to display a grid which will display the list
of records from a database table.
Group Used to group several fields under a single heading,
usually the related fields.
GuidEdit This control is used to display and edit GUIDs. You
can use the value () method to get the current GUID
value in the control.
modified () is the method
triggered whenever you
update the value in this
control.
HTML This control is used to display and edit HTML based
text. You can use setText () method of this control to
set the html content and display the content.
Int64Edit This control is used to display and edit the long
integers of 64 bit. You can use the value() method of
this control to get the current int64 value in the
control. The range of an int64 is: [-
9,223,372,036,854,775,808 to
9,223,372,036,854,775,807]
modified () is the method
triggered whenever you
update the value in this
control.
IntEdit This control is used to display and edit the integers of
32 bit. You can use the value() method of this control
to get the current int64 value in the control. The
range of an int is: [-2,147,483,648 to 2,147,483,647].
modified () is the method
triggered whenever you
update the value in this
control.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 288
Listbox This control displays the user with a list of values in
which user can make choice of selection. This is
similar to ComboBox with a simple exception, combo
will display one value at a time in the box and you
click on drop down for getting list of values whereas
list box will display few values initially and occupy
some space. You can scroll through values if you like
to see more.
selectionChange () is the
event method triggered
when you change the
selected value.
ListView The ListView control is used to present the user with
a list of options. Select one or more of them. You can
check how list view looks in the Company accounts
tab while creating Virtual Companies. You will find
multiple two list view controls in that form used for
selecting the company accounts referenced by the
virtual company. You can check the existing forms
tutorial_Form_ListControl and
tutorial_Form_ListControl_CheckBox for the usage of
controls.
You can see an example
after this text for usage of
different controls
including ListView.
ManagedHost AX supports adding the .NET controls and even
handling the events of those controls. An example
will be a scheduler control which can be added and
action like clicked () can be handled. This will be an
added advantage to use .NET controls with rich UI
interfacing. These controls are displayed using
ManagedHost.
You have various methods
to use with this control.
MenuButton This is a control that opens a sub menu which will
have a list of buttons. You use this control to provide
access to a list of buttons which are interrelated. This
can contain Button, MenuItemButton and
CommandButtons.
Usually, you dont override
any methods on
MenuButton and
MenuItemButton except in
rare case where you may
need explicit validations.
You can override the
clicked () event of these
buttons.
MenuItemButton This is a button used to open a menu item. You have
few properties like MenuItemType and
MenuItemName which are used to identify the menu
item that should be opened.
Progress This is used to display the progress of a particular
action which represent the percentage of completion
of any lengthy operation. Usually, you find a progress
control when you copy a big file from one disk or
folder to another. You can implement similar one in
AX using this control.
You can check
tutorial_Progress in forms
to find how progress bars
work or you can see
examples to use various
controls after this text.
RadioButton This is used to select one of the available options in a selectionChange () is the
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 289
group. For example, when you are given a choice to
select either Male or Female, you will be given a radio
button where you may need to select only one of
available options. You can attach data source to
provide options manually, and use the methods
selection () to find selected index, and valueStr () to
get the string value of the current selected radio
button.
event method triggered
when you change the
selected value.
RealEdit This control is used to display and edit real numbers.
You can use the method realValue () to get the
current value in the control. You can set various
properties like NoOfDecimals, ThousandSeparator
etc. for this control.
modified () is the method
triggered whenever you
update the value in this
control.
ReferenceGroup This is the control you can use to add a surrogate
foreign key field to a form. You can use this control to
enable lookup of related records. The
ReplacementFieldGroup specifies the field group that
replaces foreign key value. This control includes
lookup which you can use for selection, updation of
the value that appears in control.
You can use the
DataReference property to
specify the field in the
DataSource table that
contain the surrogate
foreign key value.
SegmentedEntry You have a special control through which you can
enter the account number and associated dimensions
as segments in a single field on the form. You can use
the DataSource and DataReference properties to bind
the data source and field in the data source table.
This control also includes lookup that can be used to
select or update the value that appears in the control.
The
ReplacementFieldGroup
specifies the field group
that replaces the foreign
key value.
StaticText This is a control used to display text or a label which
is static and cannot be changed by user.
StringEdit This control is used to display and edit strings. This
works like a common text box. text () method of the
control can be used to get the current text value in
the control.
textChange () and
modified () are the events
that can be overridden to
find when the text is
changed.
Tab This is a group control in which tab pages can be
added.
Table This is a spreadsheet control like table or a grid (not
exactly), with rows and columns.
TabPage This control is used to add a tab page in addition to
the existing tab pages in a tab.
TimeEdit This control is used to display and edit the time. You The events that will be
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 290
can use timeHours (), timeMinutes (), timeSeconds ()
or value () methods to get the current value in the
control accordingly based on the requirement.
triggered are modified ()
and textChange (). You can
override the event based
on requirement and the
working of events.
Tree This control displays a set of objects with a
hierarchical relationship in the form of the tree. You
have various methods to work with tree controls and
manage the nodes and the events.
You can see an example in
tutorial_Form_TreeControl
or see the example to use
the various controls on the
form after this text.
UtcDateTimeEdit This is similar to UtcDateTime but can be used for
date and time also.
You can see the
UtcDateTime for more
information on methods
that can be used on this
control.
Window This control is used to display bitmaps or images on a
form.
Following are few notable points while working with controls:
If you check the controls, many controls have the properties DataSource and DataField.
These properties are used to bind the control to the data source.
The control if it is bound to a data table using the data source, when you update the
value, it is persisted into the data base table and no extra code is required and are called
as Bound Controls.
You have to take care of unbound controls and write the actions for what should
happen when the value is updated or modified etc. as there will be no use just adding
the control without any events with these controls.
All the controls have their own set of methods but it is not required and recommended
to override methods for every control you add in the form. Instead, only the controls
that needs events according to your requirement should be overridden with the
methods.
You can have multiple methods for identifying similar action like textChange () and
modified () available for StringEdit which may be used in particular scenarios. You can
check them and override based on the purpose and the behavior of the events.
Few controls are introduced in AX 2012, which are not available in AX 2009 like
ManagedHost can be used for different purposes.
Following example shows you how to use various controls with sample events. This form is a
customized form generated without using template and have a large number of controls. All the controls
are unbound controls. This is just a simulation of new employee registration form which will not connect
to database and will not store data in tables:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 291
Create a simple form. To do this, right click on Forms node in AOT, Click on New > Form.
To the Caption property of Design node in form, give value Employee Registration.
Right click on Design and click on New Control > ActionPane. This will add action pane to
your form. By default, you will get an ActionTab and a ButtonGroup in action tab
wherever you create a new ActionPane. Set the property value of Caption of ActionPane
to Employee. This will get a better look to the tab.
Add a tab control to the design. You can right click and click on New Control > Tab to
create this.
Set the properties Width and Height to Column width and Column height and Style
to FastTabs. The Style property will make your tabs look like fast tabs instead of
regular tabs. You can also set them as regular tabs using other property value.
Add a tab page in Tab and set the properties Width to Column width and
FastTabExpanded of tab page to Always.
o Add a StaticText control to this tab page, set the properties Text to Employee,
ForegroundColor to Dyn LabelForeground gray and FontSize to 16.
o Create a BaseEnum with Elements Open and Close and name it CloseOpen.
Add a combo box control and set the property EnumType to CloseOpen,
created just now.
Add one more TabPage control to tab and set the value of property Caption to
General.
o Add the StringEdit control for Name and set the properties Name to
NameControl" and Label to Employee name.
o Add control DateEdit and set the properties Name to DOB and Label to Date
of birth.
o Add control Group and RadioButton in group, set the property Caption of Group
to Gender and properties Name of RadioButton to GenderRadio and EnumType
to GenderMaleFemale.
Add a TabPage for experience details and set the property Name to ExperienceDetails
and Caption to Experience.
o Add the IntEdit control and set the properties Name to Experience and Label
to Experience in years.
o Add the ListBox control and set the properties Name to Qualification and
Label to Qualification.
Add a TabPage for Nationality details. Set the property Name to NationalityDetails and
Caption to Nationality.
o Add a ComboBox set the properties Name to Nationality and Label to
Nationality.
o Add a Group, set the properties Name to PropertyGroup, Caption to Select
applicable (Drag and drop to select or unselect)."
o Add 2 ListViews to the group PropertyGroup and set the Names of ListViews to
Selected and Available and set the property ViewType to List.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 292
o Add a Group and name Others. Add 2 checkboxes and set the properties
Name and Label of one checkbox to Passport and for the other checkbox to
License.
Add a TabPage, name it Employment and set the caption Employment.
o Add a StringEdit control and set the properties Name to ZoneBelongs and
Caption to Zone.
o Add a StringEdit control and set the properties Name to TruckBelongs and
Caption to Truck.
Please note that the above fields are added only for demonstration and are not
connected to data source. You can optionally add few more fields and check how that
works.
Add 2 MenuItemButtons to the ButtonGroup in ActionPaneTab and set the property
Name and MenuItemName of the first one to ZoneTableMaster and second one to
TruckTableMaster. If these menu items are not available, please create them and
attach to forms accordingly.
Add a new ButtonGroup and one Button. Set the values of properties Name and Text to
GetEmployeeInfo.
In addition to the above property settings done for each control, set the property value
of AutoDeclaration to Yes. This makes us to use the control to use with its name while
working with X++ code. Once you have done the designing part, your form looks like as
in below image:
You can optionally set few other properties to get better look and feel of the form. With
a form design ready, its time to initialize few values to controls and get the values. The
following code will show you how to access the controls and display the values in them.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 293
To initialize the controls, the run() of form is overridden and the controls are added with
values. You can see how that is done in the following code snippet. The controls are
initialized in run() as run() is the method that initializes window. You can optionally,
choose other methods or use DataSource and DataField properties to make them
bound. Check the logic used to add items to ListView, a class FormListItem is used to add
item:
public void run()
{
FormListItem listItem;
super();
//add() is used to add an item. You can find few more methods like delete() for
deletion etc.
Qualification.add("High School Certificate");
Qualification.add("Graduated");
Qualification.add("Post Graduated");
Qualification.add("Doctorate");
//add() is used to add an item. You can find few more methods like delete() for
deletion etc.
Nationality.add("CountryX");
Nationality.add("CountryY");
Nationality.add("CountryZ");
listItem = new FormListItem("Bike");
Available.addItem(listItem);
Available.add("Car");
Available.add("Home");
}
Now, try to get the values from the controls. If you remember, you have set the
AutoDeclaration property of controls to Yes which will enable you to access the controls
from code using their name. The following code will display the values in all the controls
in an infolog as follows. You can see how all the values are obtained from the ListView
one by one. This example just displays the values in an infolog. You can write the event
based on your requirement:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 294
void clicked()
{
FormListItem listItem;
int item, counter;
super();
info(strFmt("Name : %1", NameValue.valueStr()));
info(strFmt("Name : %1", DOB.dateValue()));
info(strFmt("Gender : %1", GenderRadio.valueStr()));
info(strFmt("Experience : %1", Experience.value()));
info(strFmt("Qualification : %1", Qualification.valueStr()));
info(strFmt("Nationality : %1", Nationality.valueStr()));
counter = Selected.getCount();
for(item = 0; item < counter; item++)
{
listItem = Selected.getItem(item);
info(strFmt("Selected property : %1", listItem.text()));
}
info(strFmt("Passport : %1", Passport.value()));
info(strFmt("License : %1", License.value()));
info(strFmt("Zone : %1", ZoneBelongs.valueStr()));
info(strFmt("Truck : %1", TruckBelongs.valueStr()));
}
How do you move the items from one list view to another? You can provide a button
and move the selected item or provide a facility to drag and drop. To do this, set the
value of property DragDrop to Manual for the ListViews you created and override the
drop() method of the control Selected ListView to move the item from one ListView to
other:
public void drop(FormControl _dragSource, FormDrag _dragMode, int _x, int _y)
{
FormListItem listItem;
super(_dragSource, _dragMode, _x, _y);
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 295
if(_dragSource.name() == "Available" &&
available.getNextItem(FormListNext::Selected) != -1)
{
listItem = available.getItem(available.getNextItem(FormListNext::Selected));
selected.addItem(listItem);
available.delete(available.getNextItem(FormListNext::Selected));
}
}
Similarly, add the code for other ListView also to make the same functionality available:
public void drop(FormControl _dragSource, FormDrag _dragMode, int _x, int _y)
{
FormListItem listItem;
super(_dragSource, _dragMode, _x, _y);
if(_dragSource.name() == "Selected" && available.getNextItem(FormListNext::Selected)
!= -1)
{
listItem = Selected.getItem(Selected.getNextItem(FormListNext::Selected));
available.addItem(listItem);
Selected.delete(Selected.getNextItem(FormListNext::Selected));
}
}
Just check the above logic, its pretty simple, create a new item in other ListView and
delete from current ListView.
Finally, as the textboxes concerned to Zone and Truck will not display lookup, you have
to write the code which will display lookup. You can get a lookup in an control using one
of the three methods:
o Create a table relation and get the lookup.
o Create a form which will have no border and looks like a lookup form. Set the
value of the property FormHelp of EDT to the form you designed. Set this EDT to
the control you like to display a form lookup. These lookups are called as form
lookups. You can have a trial using the EDT ZoneId EDT, ZonesList form to create
a control in the current employee form and check. You should write code to
handle the selection and updation in the lookup control. I leave the code to you
or you can check the examples available in website given in preface.
o You can override the lookup() method and write the code to display a lookup.
The following sample shows you how to write code to get a lookup:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 296
public void lookup()
{
ZonesTable zonesTable; //Table Name
SysTableLookup sysTableLookup =
SysTableLookup::newParameters(tableNum(ZonesTable), this);
Query query = new query();
QueryBuildDataSource qbds;
qbds = query.addDataSource(tableNum(ZonesTable));
sysTableLookup.addLookupfield(fieldNum(ZonesTable, ZoneId));
sysTableLookup.addLookupfield(fieldNum(ZonesTable, Description));
sysTableLookup.parmQuery(query);
sysTableLookup.performFormLookup();
}
The logic is pretty simple, SysTableLookup class is used for displaying the lookup. You add the
query, fields you need in the lookup and call the method performFormLookup() which will display the
lookup. You can also filter for the values displayed in lookup as in the below example, which is for
displaying the lookup of Trucks.
public void lookup()
{
TruckTable truckTable; //Table Name
SysTableLookup sysTableLookup =
SysTableLookup::newParameters(tableNum(TruckTable), this);
Query query = new query();
QueryBuildDataSource qbds;
qbds = query.addDataSource(tableNum(TruckTable));
qbds.addRange(fieldNum(TruckTable,
ZoneId)).value(SysQuery::value(ZoneBelongs.valueStr()));
sysTableLookup.addLookupfield(fieldNum(TruckTable, TruckId));
sysTableLookup.addLookupfield(fieldNum(TruckTable, Description));
sysTableLookup.parmQuery(query);
sysTableLookup.performFormLookup();
}
Check how the range is added and filter works.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 297
Display and Edit modifiers
The display and edit modifiers are used with methods to create calculated controls. These
controls are just used to find some value from different table or calculate a value related to the record,
and the calculated value is displayed on the form in addition to other values. Display modifier will just
display the value and you cannot edit whereas, using edit modifier, you can edit the value in the control
which will update and save the value into the underlying database table. The following example shows
you how to use the display method:
Create a display method in ZonesTable by creating new method with the following code:
displayint64 truckCount(ZonesTable _zonesTable)
{
TruckTable truckTable;
select count(RecId) from truckTable
where truckTable.ZoneId == _zonesTable.ZoneId;
return truckTable.RecId;
}
If you check the method, its just a simple logic like any other method except that a
keyword display is prefixed to the method declaration. This indicates the method is a
display method which will acquire its properties.
Drag and drop this method into the Grid available in FormsZonesList. You can find this
grid in the group GridContainer located under Design node of the form. This will create
a control with property DataMethod of the control set to the method name. You need
to set the DataSource property to make that method fetch the data concerned to the
selected record. For the current example, set the DataSource property to ZonesTable.
Open your form. It should look as follows:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 298
Observe the count how it is displaying which is relevant to the record. Here, Zone Z1002
has 2 trucks records whereas Z1004 has 4 trucks and so on. Try an edit method in truck
table which can be used to edit zone id or zone name.
Use the display modifier on methods in the following objects:
Table methods
Form methods
Form data source methods
Report methods
Report design methods
Advantages and disadvantages of using display and edit methods:
Using more number of display and edit methods may impact performance of the
application object as for each record, the code should be executed. Instead, it is better
to use joins and write these methods explicitly if it is a hard requirement.
It is better to have simple logic which will not impact in these kinds of methods instead
of complex and time consuming logic.
Display method can be best in cases like used when you like to display one or two fields
that is referenced in another table.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 299
With enough information on the display and edit modifiers, it is time to move to advanced
controls in AX environment. The exercises on simple controls made you to understand how the basic
controls work and how to use them as bound and unbound controls. With the basic understanding on all
these its time to move a step ahead and time to see some complex controls. The following example
demonstrate how to use the ManagedHost control which will be used to add some .NET control. You
add some rich UI control which is not available in AX to the ManagedHost. Follow the below steps:
For every control you like to use, you will have a DLL file given by the developer or your
vendor. Find those DLLs and copy them in the bin folder of the AX client. The location
will be most probably C:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin
and depends on your installation path. If you observe the below image, in this example,
a third party control is taken. You can find these controls in web or you can develop one
in .NET and try this.
Add the reference of these DLLs in AX. To do this, right click on References in AOT and
click on Add reference. You will see a Browse button in the dialog. Click on that and
navigate for the DLL files and click on Open. Add all the required DLL files or you may
not get expected result and click on OK.
Add a new form to demonstrate control and name it ManagedHostDemo. Add a group
to Design node and set the values of properties Width to Column width and Height to
Column height. Set the value of property Caption to Managed Host Demo.
Now add the control by right clicking on Group, New Control > ManagedHost. In the
dialog box, you can select the control you like to add to your AX form and click OK. If
there is no reference found, you can add reference using Add Reference button from
this dialog and click on OK. Please note that if you dont have a valid reference, you
cannot add and use the control. Set the value of properties Width and Height to Column
width and Column height for this control. Just name this control as ManagedHost. You
can give a better name based on the number of controls you add and better to have
relevant name.
Now that your control is added to the form. Its time to work with the control. This
control is a scheduler control and you will see how to interact with the control.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 300
Declare a variable of your control type to get the reference of object which will give you
a handle to work with the control as follows:
public class FormRun extends ObjectRun
{
XXXX.Dynamics.Controls.SchedulerGrid scheduler;
}
In the init() method of the form, you can get the reference of the control using the
control() method of ManagedHost. You can even add the events by using the custom
event methods or standard event methods using the control as in the step 2 in the
below code. SOClicked is the X++ method in the class which is called when the particular
event is triggered.
public void init()
{
super();
scheduler = ManagedHost.control(); //Step 1
scheduler.add_SalesOrderClicked(new ManagedEventHandler(this, 'SOClicked')); //Step 2
}
Now, you can use the scheduler reference obtained from above code and add the
scheduling part as per your requirement. The following code shows how to do this. The
code is in run() method, which is most probably used for these kind of initializations.
You have to know what are the methods and events provided in the object to use them
accordingly:
public void run()
{
super();
scheduler.set_StartDate("01/01/2013");
scheduler.set_EndDate("01/07/2013");
scheduler.AddTruck("Zone1", "Truck A");
scheduler.AddTruck("Zone1", "Truck B");
scheduler.AddTruck("Zone1", "Truck C");
scheduler.AddTruck("Zone2", "Truck D");
scheduler.AddTruck("Zone2", "Truck E");
scheduler.AddSalesOrder("Truck A", "01/01/2013", "SO1001", "11:00 AM to 1:00 PM");
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 301
scheduler.AddSalesOrder("Truck A", "01/01/2013", "SO1001", "2:00 PM to 6:00 PM");
scheduler.AddSalesOrder("Truck A", "01/03/2013", "SO1001", "11:00 AM to 1:00 PM");
scheduler.AddSalesOrder("Truck A", "01/04/2013", "SO1001", "2:00 PM to 6:00 PM");
scheduler.AddSalesOrder("Truck B", "01/05/2013", "SO1001", "11:00 AM to 1:00 PM");
scheduler.AddSalesOrder("Truck C", "01/04/2013", "SO1001", "2:00 PM to 6:00 PM");
scheduler.AddSalesOrder("Truck D", "01/01/2013", "SO1001", "11:00 AM to 1:00 PM");
scheduler.AddSalesOrder("Truck D", "01/01/2013", "SO1001", "2:00 PM to 6:00 PM");
scheduler.AddSalesOrder("Truck D", "01/02/2013", "SO1001", "11:00 AM to 1:00 PM");
scheduler.AddSalesOrder("Truck E", "01/03/2013", "SO1001", "2:00 PM to 6:00 PM");
}
Above example is a sample to show how the scheduling is done using one of the
scheduler controls. This is a third party control purchased with code and customized as
per requirements. You can do in the same way and use the rich UI controls on AX forms.
Finally, add the event method to handle event as follows:
public void SOClicked(object sender, System.Windows.Controls.SelectionChangedEventArgs
e)
{
//Code to handle the event
}
You can add breakpoint and check how and when the event is called. Once you are done
with all the steps and fine with everything, you will look the working of control as in the
following image. Please note that this is just a demonstration of one of the custom
controls designed for specific purpose. Look and feel, methods and events will change
based on the control (3
rd
party control) you develop, and use.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 302
You can optionally add few properties, methods or events on the control in .NET which
can be used in AX that improves the control usability.
With a sample on using and working with ManagedHost control, lets close the controls topic
with a final control, splitter. The following image shows how the splitter is used:
Splitter is just used to split or divide the
groups on form so that you can organize the
groups and resize them. In AX, the most
common use of splitter is, split header and lines
section. Design the form as shown in the image
with a grid on header and lines, with ZonesTable
and TruckTable, link both using joins. Add
ZonesTable for top grid and TruckTable for
below grid. To create a splitter, add a group in
between both the groups and set the value of
properties of the group that will split the top
group and bottom group as below:
Name GroupSplitter
AutoDeclaration Yes
Width Column width
Height 5
AlignControl 5
FrameType Raised 3D
HideIfEmpty No
AlignChild No
Now, add the following code:
public class FormRun extends ObjectRun
{
SysFormSplitter_Y _formSplitter;
}
public void init() //Form init()
{
super();
_formSplitter = new SysFormSplitter_Y(groupSplitter, groupLeft, element);
}
int mouseDown(int x, int y, int button, boolean ctrl, boolean shift) //Event of GroupSplitter
{
int ret;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 303
ret = super(x, y, button, ctrl, shift);
Return _formSplitter.mouseDown(x, y, button, ctrl, shift);
}
int mouseMove(int x, int y, int button, boolean ctrl, boolean shift) //Event of GroupSplitter
{
int ret;
ret = super(x, y, button, ctrl, shift);
Return _formSplitter.mouseMove(x, y, button, ctrl, shift);
}
int mouseUp(int x, int y, int button, boolean ctrl, boolean shift) //Event of GroupSplitter
{
int ret;
ret = super(x, y, button, ctrl, shift);
Return _formSplitter.mouseUp(x, y, button, ctrl, shift);
}
Its just very simple, the class SysFormSplitter_Y is used to add splitter that splits the form
horizontally and SysFormSplitter_X can be used to split vertically. You can check this and try once with
different variations. With enough information on various controls, I leave the extra practice to you to
work with other controls and you can always check the site for more updates and exercises mentioned
in the preface for more information. Coming text explain some interesting features which are useful
throughout your programming practices.
There is an interesting class called Args available in AX. Lets try to understand what is Args and
use of the Args class. Args class is used to pass arguments between different application objects.
Consider a scenario where you need to pass some arguments while opening a form from code or
another form. You cannot pass arguments directly. Instead, you use the Args class and pass the
arguments.
Most of the objects including forms, reports and queries etc. use Args object as the argument in
the constructor to initialize the values. You can construct the Args class object and set the values using
the methods available in args object. Following are some of the methods most commonly used with Args
object:
The parm() method, which is used to pass string.
The parmObject(), which is used to pass the object of any type.
The record() method which is used to pass record of any type.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 304
The parmEnum() and parmEnumType() methods, used to pass enumeration values in
args.
To get the args object in the application object, you can use the args() method available as part
of that application object and get the Args instance which can be used to find the arguments passed by
caller.
Args object can be created by user and pass using the code and it is created and passed to called
object from caller object when you use menu items or similar kind of to open the application object. In
that case, the Parameters, Enums attached to menu item will be set by the kernel to the args object
which can be used in called form. The following example shows how to call a form using X++ code. There
is no particular business case behind this example and is just a demo:
static void Job6(Args _args)
{
Args args;
FormRun formRun;
TruckTable truckTable;
select truckTable where truckTable.TruckId == "T0001";
Args = new Args(formStr(ZonesList)); //Args created passing the form object.
formRun = ClassFactory.formRunClass(args); //Getting FormRun instance passing the args.
args.record(truckTable); //Setting the record in the args to pass to the form.
args.parm("Sent from TruckTable"); //Setting the string in the args to pass to the form.
formRun.init(); //Initializing the form.
formRun.run(); //Run the form.
formRun.wait(); //Keep the process in pause state or it will close immediately. You can check
//once commenting this code to understand the behavior of the form.
}
Override the init() method of the form ZonesList and write the following code to get the args
and the values passed through args:
public void init()
{
TruckTable truckTableRecord;
Args args;
super();
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 305
args = element.args(); //Used to get the Args object passed by caller.
if(args.record().TableId == tableNum(TruckTable)) //Used to check the table id of the record.
{
truckTableRecord = args.record(); //Used to get the record.
info(strFmt("%1, %2", truckTableRecord.TruckId, truckTableRecord.Description));
info(strFmt("String sent is : %1", args.parm())); //Get the string using parm()
}
}
In the above example, you can see how the args is used to pass values from caller and read the
values in the called. You can use more methods available in Args for more interfacing between
application objects.
Note: If you dont check for proper caller and records, you may get errors due to a fact that multiple
callers may call the same object using different args. You can find the example on how to check, find and
validate the caller in an example given in above exercises. Just, args.caller() will return the caller of the
current object.
Now, lets understand about some standard existing forms and how to identify the form name
etc. you need. Navigate to the path <Company>>Accounts receivable>Common>Customers>All
customers. You will see the following form. This is a customer master list page:
Just consider a case where you need to customize this form like adding few fields or updating
some controls or buttons based on your business case. To do this, you need to identify the form first. To
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 306
do this, right click on the form, you will see a context menu as shown in adjacent
image. In the menu, you can find few interesting menu items. The use of menu
items is as follows:
Filter by field and Filter by selection are used to filter the
records based on the values.
Sorting options are available where you can sort based on a
column value in either ascending or descending order.
Hide will hide the particular column. This modification is user
specific and can be reverted by user going to Tools > Options > Usage Data, where you
will find all the usage data of the current user. Once if you hide any fields, you can find
Show menu item in which the fields will be shown and you can show the hidden fields
using this menu.
Record info can be used to show the complete information of the selected record. This
will have all the columns data which can be seen for reference.
Personalize is the most powerful tool you can use for personalizing form or identifying
the form and query and other stuff. You can see the dialog that is opened when you
click on this menu item:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 307
The dialog will have 3 tabs namely Layout, Information and Query.
Layout will have the information of the physical layout, look and feel of the form. You
can modify the layout of the form and this feature of allowing modifications to end user
as per the user requirement is allowed using the technology IntelliMorph. Consider a
case where an end user likes to have one more field on the form in addition to the
available fields or hide a column which is not required or change the order of the
columns on the form. Every time for these small requirements, end user cannot depend
on a developer or they cannot learn development of objects which is even risky as
object level modification will reflect every user. In order to get out of these risks and let
end user allow updates by themselves, IntelliMorph provides the facility that makes it
simple and easy to modify forms and reports. IntelliMorph makes it possible for users to
hide and show fields on forms, move fields between tab pages and even add new fields
to forms. Individual users can safely and swiftly rearrange fields to best suit their
individual work preferences without having to modify the form through the Application
Object Tree (AOT). Just use the properties as seen in the properties group and you can
edit the forms without opening the form in AOT. You can even add fields using Add
Fields button. IntelliMorph will ensure a consistent and well-arranged display of the
application so all the developer needs to specify is the structure of the user interface.
With IntelliMorph you can quickly implement design changes to different forms and
reports. Field labels can be modified without disturbing field data or underlying business
logic. If you switch languages on an invoice form, IntelliMorph automatically resizes the
field labels to fit the words.
Information tab will show the information related to the form like name of the form,
caller information and menu item information. You can even edit these objects directly
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 308
clicking on Edit which will open the object in development environment to edit. This can
be used by developer to identify quickly about the information of object developer is
using and need to customize.
Finally, Query will display the query used by this form. As discussed previously, query in
a form is the collection of data sources which is organized in a well-mannered way to
access the data from database tables and display on the
form.
In addition to the above menu items, you may get an advanced
menu which will have few more menu items when you open few forms
like the master or details etc. forms. You can find the menu list in the
image.
You can find an interesting menu, View details which will open the
master form of the related field. To make this work, you have to set the
FormRef property of master table to the display menu item created for
that particular master. This property should be assigned the display menu
item of the related form. This will be a very useful tool to open the master
form directly related to that particular field. You can check for ZonesTable
setting this property and try to open the same from TruckTableMaster
form right clicking on the ZoneId field and click on View details menu. You
should find the ZonesTableMaster opened for you.
Finally, you can see a menu Create alert rule, which can be used to create alerts i.e. notifications
kind of actions and configure the users who should receive those alerts when an action is done on the
table. You can check more clicking which is pretty simple. Just click on New in the dialog and select the
values in the columns accordingly and save the record. You can also open the list of Notifications using
this form.
Now that you know how to get the data from end user, how to provide the UI with various kinds
of facilities available, how to use the various controls available and you also know how to use the rich UI
controls developed in .NET. Finally, you learned what are all the ways to provide lookups to users and
how to update form look and feel without opening and modifying the object in AOT in addition to
creation of alerts. Finally, I like to request you to work spend enough time on forms and try few others
which are not discussed due to the constraints and importance of the other topics yet to come.
With enough information on forms, its time to move to reporting. An end user likes to see the
report which can be used to take hard copy of send emails etc. The coming topic discuss all the
possibilities to generate reports. This topic will cover standard reporting in AX 2009 and SSRS reports. I
request you to read this topic until you are clear as AX has more constructs for development of SSRS
reports which is a great advantage while work with reporting in AX 2012.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 309
Reports
Any technology which stores and process data should have 2 facilities that should be given for
end users, UI for doing CRUD operations and Reporting. AX provides most robust mechanisms to
develop reports. We will discuss the possibilities of development of reports in detail in this chapter.
Reports stand one among top in the list of most important objects in Microsoft Dynamics AX as
they are often used by users who do not normally access the system, and so need to run a report to
view data. Reports are non-editable. Some users may prefer to have a printed hard copy of the data
within Microsoft Dynamics AX to work with during their day to day routines. AX supports standard
reports and SQL Server Reporting Services reports, often called SSRS reports. Most probably standard
reports are used in AX 2009 and continued to develop standard reports in AX 2009 though there is a
provision to develop and use SSRS reports. In AX 2012, only SSRS reports are used due to the advantages
of SSRS, though the support continued for standard reports. We will see both the ways of generating the
reports. We will discuss a good part of standard reports first and jump to SSRS reports as still many
organizations are using standard AX reports due to fact that they are working with AX 4.0 and AX 2009.
We will not see complete information as they are not used from AX 2012 onwards but a majority part
will be covered.
Standard AX reports are based on a query and a design stored in the AOT. You can use the
Reports node in AOT to design the reports. You can use another way of designing reports using the
wizard which is the easiest way of generating the report. Any standard report in AX will have three
sections that comprise of the following parts (please note that the standard report architecture is
explained keeping AX 2009 in view). You can find the difference between the nodes in given images:
Methods
Data sources
Designs
One node introduced in AX 2012,
Permissions.
Lets discuss the significance of each node in
detail as follows:
Methods: This node is used to write the methods on the report to make the report work
interactive. Mostly, the methods are used to write the logic to fetch and filter the data
or to perform calculations etc.
Data sources: This node will have the query used to fetch the data for the report. This
section of the report contains the definition of the query. The structure is similar to the
independent queries in the AOT and the AOT query can also be used if the query
supports your scenario instead of designing one more query. You can define ranges,
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 310
sorting options and also aggregates that are used with Group By can be defined in the
queries of the Reports.
Designs: The layout of the report is designed in this node. You can have multiple designs
and each design can have multiple sections as per your design requirement of the
report.
Permissions: This node is used to set the permissions on objects similarly like in Forms.
Reports are mainly used for printing information from the database and focuses on the
following:
Fetch data
Filter data
Sort the data
Aggregate data
Present the data
While programming with Reports, you can use some handles to access different objects in
reports as did in forms. All the methods in report is concerned to some or the other object. These
methods work like events when you do a particular action. You can always use the this keyword for
accessing the methods object for example, if you are in a method of a label, this refers to the label. The
below table will show you how to access an object. These objects are mostly used if you are not in the
method of object in which you are trying to access the object:
Object Handle to access from X++
Report element keyword can be used throughout the form to refer
report
QueryRun queryRun can be used for this.
Current record in data source <name of data source in the query> is used for this.
ReportDesign If AutoDeclaration is Yes, you can access directly using the name
of the design or you can use the method
ReportRun.design(<name given to design>). This is mostly used
when you have multiple designs.
Section of a report You can use the name directly when the property
AutoDeclaration is set to Yes or use the method
ReportSectionGroup.Section(<sectionType>)
Control in the report Name assigned to control, when property
AutoDeclaration is set to Yes
or
reportSection.control(<fieldId>)
PrinterJobSettings You may optionally use the printer settings and this can be done
using ReportRun.PrintJobSettings()
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 311
Designs in Standard AX reports:
Standard AX reports have 2 types of designs. They are AutoDesign and GeneratedDesign. Each of
the design specifications have its own advantages as follows:
Auto Design specifies the fields to include in a report and the layout is decided at runtime after
the user specifies the sorting and subtotals. So, in this case, user can control the structure of the report
where developer lacks control over report layout as it is generated at runtime. There is an added
advantage where these designs can use auto design which are evaluated at runtime and a change in the
template will reflect all the reports that use the template.
Coming to the case of generated design, you can specify the fields and the specific layout of the
report. The end user will have less flexibility in this design as the developer specifies sum structure
which gives an added advantage to developer where the report and layout will be in control of
developer and is presented exactly as implemented by developer. Please note that if you add both types
of designs to a single report, Generated Design is used.
When you design any kind of report, you can use different kinds of sections available. The
following table describe the list of sections and the usage of each section:
Prolog This appears at the beginning of the report and is used to display
items like logo or title of the report. In fact this is printed before
the page header on the first page and will be displayed only on
first page.
Page Header This is displayed on top of every page. You can display page
number etc. on this.
Header This is available only for generated designs and appears at the
beginning of a new group of records which is used to display the
group name etc.
Section Group This is available only in Generated Design which is used to group
Body of the report to structure the data sources usually.
Footer This is also available only for Generated Designs which appears
at end of group of records which may be used to display sub
totals or aggregates.
Page Footer This is displayed at the bottom of every page which can be used
to display items like signatures or page numbers, dates etc.
Epilog This appears at the end of the report only on the last page and
once. You use this to display information like logos or end notes
or summaries etc.
Programmable Section These are special sections that can be displayed any number of
times and at any place. This is used to present information
dynamically on the report and is activated explicitly using X++
code.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 312
Standard AX reports can be designed in one of the 2 ways. First one is using the wizard and the
other way is manual approach. Wizard is most easiest way but has some limitations compared with
manual approach. The following steps describe the approach using wizard first and got to the manual
approach. The following report will generate the report for all the trucks available in the TruckTable.
Follow the below steps to generate the report.
Find and click on Tools > Development Tools > Wizards > Report Wizard. You may
encounter terminology called labels etc. Dont worry about the labels, you learn how to
create and use the labels as a full-fledged separate topic which has its own advantage.
Click Next on the first screen. You can select any optional check boxes available.
Give Name and Caption accordingly. Check the Auto create label and the Label file ID.
This will create the labels automatically. You will learn more about the labels and the
uses in separate section. You can visit the topic on labels and come back if you feel any
confusion which will not break your exercise in anyways. Click on Next once you are
done with the settings.
Select the tables you like to add data sources to the report. You can select multiple
tables based on requirement. The current report display list of trucks so, just select
TruckTable from All tables list to selected tables list. You can deselect tables which are
selected by mistake or deselect all tables from selected list. Once done, click on Next.
In this screen, select the list of fields you like to display on the report. This should look
as follows after you select the required fields:
You can use the Up and Down buttons in addition to Select, Deselect and Deselect all.
Once you are done, click on Next. Please note that at any point of time, you can click
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 313
Finish at any time but not advised as you may miss few steps which will be useful for
few settings.
You will be given summation fields in this step. You can use them for summation like the
aggregate functions. Select TruckVolume which can be used to find the overall volume
of all the trucks in the organization and click Next.
You will get the screen for Sort fields in which you can select the fields that are used to
sort and order the records on the form. Select Truck id, you can see the order radio
button which is used to select ascending or descending order that can be selected. Click
on Next once you are done with selection.
You can optionally add sub headings which will be presented in the current screen. This
sub heading will be printed when the value in one of the selected sort fields changes.
Uncheck the selected subheading field and click on Next. You can check this and find the
output of report to understand what will be the look and feel of the report.
This screen will have Group totals, which will be printed when the value in one of the
selected sort field change. Uncheck the selected as we dont need any group totals for
this report. You can optionally check this to understand what will be the look and feel of
the report and click on Next.
The important requirement for any report, Range can be added in the current screen.
Select TruckId and TruckType in the ranges and click on Next. Your screen for ranges
looks as in the below image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 314
Select the report layout which can be either Tabular or Columnar and you can also set
the page orientation to Portrait or Landscape. Finally, you will find a combo to set the
template of the report. The templates are covered in coming text. You have quick look
at the templates section and come
back to this which will not break the
continuity or you can try this and
jump to templates. Set one of the
templates and click on Next.
You can preview the report in this
screen and select menu settings. If
you select report to be opened from
an existing menu, you have to select
a menu item in the next screen and
click on Finish. Otherwise, just click
on Next and click Finish in the last
screen.
The design of your report in AOT
looks like as shown in the image:
You can set few properties of the report which are shown in the adjacent image. You
can also find various properties for report query
and data sources which will be similar to the
properties of query and ReportDesign which are
used to update the design accordingly.
The property Interactive of report is used to
display a dialog to user where they can select the
medium of print with print settings and options.
You can set the query property Interactive to enable or disable displaying of
SysQueryForm which can be used to change the query at runtime by selecting the
ranges and sorting options.
You have few properties for ReportDesign which can be used to update the design of
the report. Set the caption property to Trucks. You can also set various other
properties like Font, Color Schemes etc. You can find one more interesting property in
addition to the above said properties, PrintFormName which is used to display the
interactive form of the report. The value should be SysPrintForm and if selected nothing,
it will not show you any interactive form for the report. You can optionally select other
forms available to check the variations.
If the users dont have permissions to view report, they may need an alert message to
get the same information. To set this, you can use AllowCheck property. If set to Yes,
you will see alert message and if No, you will not see any security error message even if
you dont have permissions.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 315
Finally, AutoJoin property specifies whether a record returned by the element.args
method is used to set the range on the report query. This will automatically enable join
feature which will reduce code and effort.
Create a menu item of type Output and open the report. You will see the interactive
dialog of query. Click OK, you will see the interactive dialog of report which appears as
shown in the below image:
You select print medium and other page settings and print options of the report in this
form and click OK to generate the report. Once you are done with everything, your
report should look as in the below image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 316
If you remember in the wizard, a template was set in one of the steps in the report. Lets try to
understand what report templates are.
Report Templates
A template is a model that specifies layout or structure of the object that should be presented.
Report Templates in AX are used to specify how the design of report should look. Templates can be
created in AX using Report Templates node under Reports node of AOT. A report template can specify
the sections that a report includes, such as a page header and page footer, the controls included in each
section, and the layout of the controls. One great advantage using report templates is you can have
common look and feel if you use the report templates for report designs. The design of all the reports
looks alike with same layout when you apply the same template. You set the report template to the
report design using the property ReportTemplate.
The following steps help you to create a new report template:
In the AOT, expand the Reports node, right-click Report Templates, and then click New
Report Template. This will create a new report template. Set the properties Name and
Caption of newly created report template accordingly.
Right-click template created in above step and click on New, and then click a section to
add it to the template.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 317
You can also add controls to the section by right-clicking the section, click New Control,
and then click a control to add it to the section. You can add any number of controls by
repeating this step to add additional controls.
You can add an image to the report template by using a Bitmap control which can be
used to display logo of organization etc.
To modify control and section properties, right-click the control or a section, and then
click Properties. You can find different properties that can be used to update look and
feel of the report.
Click Save to save the changes.
To check how the report looks
with your new template, set
this report template for one of your reports using the property ReportTemplate of
design.
With the basic knowledge on how to design a report using wizard, its time to move to methods
of the reports. Reports too have methods which can be used as events. These methods are used to
perform the actions like, initializations, filtering the data etc. and can be overridden in particular cases
when you like to have control over reporting and supplement or modify and extend the standard
execution of the report. In addition to the existing methods, you can also write new methods in reports.
The new methods which you write are not called by default and you have to call them. Following is the
list of most important methods on a report:
Method Usage description
init () This method initialize the report and the objects in the report. You
can override this method to perform initializations, if any to be done
in your report manually, which are used to modify or update the
report layout or the data that will be fetched.
dialog () This is used to display a dialog with fields which are used in program
to get inputs from user and filtering the data.
fetch () The core purpose of this method is, initialize the queryRun, fetch the
data from the query and send the data to the report. This method is
overridden usually to filter the records based on logic or to perform
any complex calculations before the data is sent to the report. This
method should be overridden when you use a temporary table as
the report data source which should be populated to get the records
on the report.
send () This method is activated every time a record is to be printed on the
report. This is not overridden most commonly but can be done when
a record level manipulation should be done, which is used instead of
fetch ().
executeSection() This method is found on the sections which will be executed every
time the section is executed. This is overridden when you like to
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 318
dynamically change the controls and the values in the controls or to
work with programmable sections.
run() Runs the report. This method is executed when the user clicks OK on
the preliminary specification dialog box of the report (which enables
the user to select where to send the report, and so on). This will
usually call the fetch() and print() methods
prompt() Prompts the user to select the print medium and other information.
Please note that the query method and report method are entirely
different though the names are same. You can override this method
and comment super() to disable this selection permanently.
The methods in reports are called in the following order. This is typically called as method calling
sequence which is familiar to you in tables and forms:
init() of form
run() of form
fetch() of form from super() call of run()
prompt() of form from super() of fetch()
send() of form from super() of fetch() for each record
print() of form from super() of run()
Now, lets create a report with few basic requirements that will cover few advanced concepts in
reports like using methods and other controls. The requirement is as follows:
a) Report should display the Trucks and Zones info i.e. you will use multiple data sources.
b) Company like to check if the zone is big and display Zone is big in the print with red in
color that will inform the truck schedulers to take care about time management.
c) Company likes to calculate and display the number of trucks available in zone in addition
to the details on the report.
As you know how to add the data sources to query, you can add any number of data sources.
For (b), you may need to find whether the zone is big or not and display a text string with the content
given. To do this you are given a section, Programmable Section which will be displayed when you
request to show this control at that particular place. You will understand how the programmable
sections benefit while developing the reports. Finally, (c) is done using the display method. Note that,
you can write the display methods in reports for any calculations and display them on the report. Follow
the below steps to design the report as per requirements:
Create a report and name it as ZonesTruck.
Expand the Data Sources node, in the data sources of query, drag and drop the
ZonesTable. Set the value of property Dynamic of Fields node to Yes. You will find the
Fields node in the data sources.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 319
Add a child data source to the ZonesTable data source by drag and drop the TruckTable
and set the value of property Dynamic to Yes of Fields node in the data source. In
addition to this property set the value of property Relations to Yes for the data source,
which will use the relations from underlying table.
As your query is ready, its time to move to the
design part. Right click on Designs and click on
New Report Design. Your report looks as shown
in the image. Note that by default, you are able
to see the AutoDesignSpecs. You can add
generated design to the report design by right
clicking on the ReportDesign1 and click on
Generate Design.
Set the value of property ReportTemplate of ReportDesign1 to FrontPage or some other
template you like to.
Add Body to the AutoDesignSpecs by right clicking the AutoDesignSpecs and click on
New > Body.
You can add the fields/controls to the report in either of two ways, one is, add the new
controls to the Body by right clicking on Body and click on New Control > Select the
control you like to add. You can add fields groups or fields of the data sources added to
the report using the same method. Second way is, drag and drop the fields in data
sources on to the body.
You can optionally add sections if required based on your design requirements. Now,
add a Programmable Section to the design and add a Text control to the Programmable
Section. Set the values of properties Text to Zone is big. and Color to Red.
To show and hide this Programmable section, we have to use execute () method which
will paste the section whenever we call execute for that particular programmable
section. You pass the control number to do this. Override the fetch () method to do this
as follows. You can override these from methods node of the form:
public class ReportRun extends ObjectRun
{
QueryRun qR;
ZonesTable zonesTable;
TruckTable truckTable;
}
public boolean fetch()
{
qR = new QueryRun(this.query());
while(qR.next())
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 320
{
zonesTable = qR.getNo(1);
truckTable = qR.getNo(2);
this.send(zonesTable);
this.send(truckTable);
if(zonesTable.ZoneType == ZoneType::Big)
{
element.execute(1);
}
}
return true;
}
Check the above program, in fetch, you are getting the zonesTable and truckTable
records and sending them to the report, which will display the fields' values of records in
corresponding fields on report. You can see the method execute() called with value 1.
This 1 represents the control number and when the zone type is Big, this execute call
will display the corresponding section on the report.
Finally, add a display method with the following code in the form and drag this method
in addition to the list of fields in body of the design and give a good label that suits the
purpose like Count of Trucks. This method counts the number of trucks in the current
zone and return the count:
displayint64 truckCount()
{
TruckTable tTruckTable;
select count(RecId) from tTruckTable
where tTruckTable.ZoneId == zonesTable.ZoneId;
return tTruckTable.RecId;
}
Once you are done with all the required steps, your report looks as in the following
image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 321
You can optionally change the look and feel of the report by manually setting few
properties of the sections and fields and can use the generated design to add multiple
sections and get better control over report.
The following notable points may help while developing the standard AX reports:
You can use the temporary tables to get the data onto the report. You have to populate
the data into the temporary table and send that to report for getting the data to the
report.
Though AX 2012 supports the standard reports, it is always recommended to go through
only SSRS reports instead of standard reports.
You can open the reports using X++ code as you did for forms if you have any kind of
scenario.
You are advised to use the best practices while working with large volume of data to
avoid the performance issues while generating the report.
SQL Server Reporting Services
Microsoft SQL Server Reporting Services (SSRS) delivers web-enabled reporting functionality
available across the enterprise, so that you can create reports that draw content from a variety of data
sources, publish reports in various formats, and centrally manage security and subscriptions. An
interesting fact is, SSRS ships as part of Microsoft SQL Server and requires no additional licensing which
is also a cost effective choice.
The most notable change in Microsoft Dynamics AX is the migration of the reporting framework
from the X++ reporting framework to Microsoft SQL Server Reporting Services. This brought a
tremendous change in the area of reporting in Microsoft Dynamics AX 2012 where you can leverage the
features of reporting in AX 2012 using the capabilities of BI reporting technologies. Although the basic
reporting capabilities of Microsoft Dynamics AX remain intact, and simple reports might look the same
on the surface, this is a significant change for developers and partners who create or work with reports
on a daily basis.
SSRS offers great improvements over standard X++ reporting framework used in AX 2009. Many
components, classes and objects are introduced especially for reporting in AX 2012 which will improve
in terms of both performance and features. As a core component of the Microsoft BI platform, SQL
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 322
Server Reporting Services enables you to take advantage of the Microsoft BI technology stack including
the performance and services within SQL Server for analytics and reporting, introducing richer reporting
capabilities that simply arent possible in X++ and may need great effort.
Though you can develop and add the SSRS reports in AX 2009 also, the new framework and
features introduced in AX 2012 will make you excited if you are SSRS user or developer or a fresher who
likes to migrate to SSRS reporting. Microsoft Dynamics AX 2012 gives you the ability to expand or
collapse lists, drill up or down through data, and dynamically highlight values in reports. By using the
new reporting framework, you can work with advanced BI functionality through SQL Server Analysis
Services and view ERP information alongside online analytical processing (OLAP) data and other
statistics..
With Microsoft Dynamics AX 2012 SQL Server Reporting Services you get a wide range of ready-
to-use tools and services to help you create, deploy, and manage reports for your organization, as well
as programming features that enable you to extend and customize your reporting functionality.
Reporting Services tools work in Microsoft Visual Studio environment and are fully integrated
with SQL Server tools and components. Whatever the AX reports developed in Visual Studio can be
added to AOT in AX 2012 and can be deployed in AX environment. Reporting Services is a server-based
reporting platform. These services provide comprehensive reporting functionality for a variety of data
sources and include a complete set of tools for you to create, manage, and deliver reports, and the APIs
available in AX 2012 enables the developers to integrate or extend data and report processing in
application.
SSRS Reports can include rich UI which will be used for data visualization, including charts, maps
and other graphical elements. With Reporting Services, you can create interactive, tabular, graphical, or
free-form reports from relational, multidimensional, or XML-based data sources. The Microsoft
Dynamics AX 2012 database can be one of the data sources from which SSRS draws its content. You can
select from a variety of viewing formats and you can view even if the AX client is not installed in the
environment using the Internet Explorer and configure reports to be displayed in Enterprise Portals and
Role Centers or export reports to other applications such as Microsoft Excel. The reports that you create
can be viewed over a Web-based connection or as part of a Microsoft Windows application or
SharePoint site as discussed. You can also receive email messages when report data changes creating
data alerts on reports published to a SharePoint site.
Model based approach is used in Microsoft Dynamics AX 2012 for creating reports using project
templates and modeling tools that are incorporated into the Microsoft Visual Studio development
environment. The features that are provided as part of Microsoft Dynamics AX 2012 are tightly
integrated with SSRS. You can also add the Visual Studio projects in Microsoft Dynamics AX 2012. The
major advantage is, data can also be displayed in the Role Center pages in Microsoft Dynamics AX 2012
where users can display reports or graphical data. The major advantage in development of reports is,
you use the highly user friendly visual studio environment to develop the SSRS reports with rich UI.As
you can understand from the long explanation, SSRS in AX 2012 uses Model View Control [MVC]
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 323
architecture. This architecture separates the logic of displaying the report and update UI interface,
working with data and controlling the report to change, like changing the flow of data between model
and view. In fact, Model View controller is a pattern to de-couple the business logic from the user
interface with retrieval and controlling of report. The following statements explain you about the MVC
in terms of terminology:
Model: This part of the report is responsible for retrieving of data and operation
performed on data with respect to business logic. Queries and communication with
data layer is contained in this part. The advantage of using this part is, you can update
this part at any time without editing the design to update the data retrieval.
View : This part is responsible for user interface which specifies how to display data on
the report. You can design the view capable of showing the data on different views. The
advantage is, you can update this part at any time without doing any modification of
retrieval part.
Controller: This part controls the data flow between Model and View. You can update or
control the communication between the View and Model using the controllers.
Controllers can also update the Model for its data which will benefit in many situations
and gives more control on report.
You have various framework classes including SrsReportDataContract,
SrsReportDataContractUIBuilder and SrsReportRunController which are used for
providing this functionality which will be covered in coming text.
The following image shows you the basic Model View Controller [MVC] architecture followed by
SSRS Reports:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 324
Finally, the security of reporting in AX 2012, when you use SSRS, is integrated with Microsoft
Dynamics AX 2012 security which provides the complete security as provided for any other objects in
AOT. SSRS uses Microsoft Dynamics AX 2012 security to control access to the report data.
With a small introduction of SSRS reports and the services, its time to move to the actual SSRS
reporting in AX 2012. Reporting services itself, being a huge topic to be covered in any technology is a
key technology that should be learned. This text covers majority of development, using and deployment
of SSRS reports in Visual Studio as well as the framework facilities provided in AX 2012. First we will start
with a simple report and graduate to the complex reports using the framework features. Before going to
start reporting, to work with the reporting, create 3 tables namely BankAccounts, BankCard and
CardTrans. You can understand the use of each table looking the names. These tables are created in
previous sections and you can recreate them for current topic if you lost them. The following image will
show you the list of fields in each table. Please note that the table BankCard extends the BankAccounts
table. You can refer the Table Inheritance section and come back if you need any help on how to use
table inheritance. The CardTrans table has a foreign key relation with BankCard table and stores the
transactions done using Card.
As you are prepared with the required data for reporting by this time, its time to move into the
world of AX reporting. To develop an SSRS report for AX 2012, you need to install BI components
especially, SSRS Reporting Extensions and Visual Studio tools. You can find the installation in appendixes
as reference. The following image shows the installation screen selected with the required options:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 325
Once you are done installing, the BI components and Visual Studio tools, you can move to
development and working of SSRS Reports for Microsoft Dynamics AX 2012. You can optionally select
and install Analysis Services Configuration for AX 2012 though not mandatory, but can be used to
integrate the analysis services into AX. To verify whether the BI Components are installed successfully
and report servers are correctly configured in MS Dynamics AX or not, you can go to the form Tools > BI
tools > Reports servers. You can open the same form from System Administration > Setup > Business
intelligence > Reporting services > Report servers. Configuration form looks as shown below:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 326
Just verify all the fields and validate the settings and
close. If they are not validated you may need to take a look at
SSRS configuration of your SQL Server. Once you are prepared
with everything, you can start development of SSRS reports. You
can locate the SSRS reports under the node SSRS Reports of AOT
and the actual Visual Studio projects of the reports in Dynamics
AX Model Projects of Visual Studio Projects node in AOT. You can
find them as shown in the image. If you like to edit an existing
report, you will open it in Visual Studio locating the project from
Dynamics AX Model Projects and when you add the report to
AOT, it is added to Reports node under SSRS Reports of AOT.
SSRS Reports for AX 2012 can be created in various ways in which most commonly used
methods are, using queries which is the simplest form and using report data provider classes which is
using X++ programming using the Framework classes given in AX 2012. This topic covers both the ways
up to the mark using all the possibilities in using them. In addition to these, you may also use the
Analysis services which uses the cubes for generating the report and is out of the scope. To create a
sample report, follow the steps as below:
Create a query for the table BankCard and set the properties accordingly, which is used
to create a query based SSRS report.
Now, open the Visual Studio environment and click New > Project in File menu. In the
templates, you should see a template with name Microsoft Dynamics AX which will have
couple of templates most usually, Report Model and EP Web Application. Report Model
is used for designing reports for Microsoft Dynamics AX and EP Web Application are
used to develop the parts for Enterprise Portal for AX.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 327
Select the Report Model, change the name to BankCardReport and click on OK. Your
screen looks like as shown in the below image:
If you cannot see the above template in Installed Templates, you should check for the
installation errors and try to install/configure the reporting services and Visual Studio
tools and come back again.
Once you create a project with the report,
your Solution Explorer window looks as
shown in the image. This will not have any
reports added initially. You should add a
report to this project.
To add a report to this project, right click
on the project select Add and click on
Report. This will add the report to your
project. Give a good name to the report,
like, BankCardQueryBasedReport. Once
you add the report, your solution explorer
looks as shown in the image.
In the left pane, you will see the report
items. You can double click on report if they are not visible. The screen looks as in the
below image, with all the report items of the report you added to the project:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 328
Each of the item has its own significance as follows:
Datasets: The report dataset is used to retrieve the data from the AOT. This will
have many options. The Query option is used to specify an AOT query that is used as
data source whereas Report Data Provider is used to specify a data processor class
as the data set. All fields available in AOT query are shown in the report dataset and
are referenced in the report design to retrieve and show data on the report.
Designs: This will have the layout or design of the report on which the data will be
displayed after retrieving the data from AX. Designs are 2 types namely, Auto Design
and Precision Design. Auto design is a report design that is automatically generated
based on the report data. An auto design is created by using drag-and-drop actions,
and selecting elements by using Model Editor. This is the easiest way and simplest
way to create reports. You can create simpler reports very quickly using this design.
Precision design is a report design that is created by using SQL Report Designer.
Precision designs are useful when a very precise layout is required like Bank Check
or a registration form of employee.
Images: Any embedded images that you need to display on SSRS report are added in
this node.
Data Methods: Business logic written in C# is found under this node. Data methods
are no more recommended in SSRS due to the complexities.
Parameters: The report parameters which are to be displayed on SSRS report are
added under this node. Users can filter the report based on those parameters.
Now, add the Dataset to the report. To do this, right click on Datasets node click on Add
Dataset. You will see a new dataset created with the name Dataset1. Rename the
dataset accordingly probably, BankCardDS in the current example. You have to refer
this dataset to AOT query. To do this, go to the properties of the dataset and set the
property value of Data Source Type to Query and select the Query in the query
property. To do this, click on ellipsis button available attached to the box of Query
property. When you click on ellipsis, you will see the forms as shown in images in the
wizard one after other:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 329
Find the AOT Query you need, BankCard in this case and click on Next. In the next screen
select all the fields or the required fields and click on OK. You can see various nodes
available in the screen. Observe the nodes once, you will find fields in addition to field
groups and display methods.
Expand the Fields node under BankCardDS dataset, you will see the list of selected fields
in the query in last step. These fields are used on the design from which data is
displayed on the report while report is being executed.
To render a report, you need the design. To add a design to the report, right click on
Designs and click on Add >Auto Design. You will find one more design, Precision Design.
Using Auto Design, you can simply design report by drag and drop. Precision Design is
explicitly used when you need to place the fields and get a desired layout other than
tabular or columnar etc. available in Auto Design. Once you add the design, give a
relevant name and select the LayoutTemplate property to
ReportLayoutStyleTemplateNoCompany.
Right click on the report design just created and click on Add > Table. In the current
example, we will display the records in tabular form. You can select List, Matrix etc. for
rendering the report based on the requirement. Update the value of properties Name to
BankCardDSTabularView, StyleTemplate to TableStyleTemplate and Dataset to
BankCardDS. Expand the Data node to check the fields. You should see the list of all
the fields added to this. You can optionally delete the fields not required like RecId,
dataAreaId etc.
Once you are done with all the above steps, your report design should look as in the
image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 330
You can preview the report layout using the Preview button that can be seen in tools in
the above image.
Finally, its time to add the report to AX i.e. AOT. To do this, right click on
BankCardReport, the project and click on Add BankCardReport to AOT. You can see the
menu in the following image:
It may take some time to add the report and project to AOT. Once you see the message
Add to AOT Succeeded. in status bar, open the AX environment and check AOT, you
will see the report added in SSRS Reports and project added under Dynamics AX Model
Projects node as in the below image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 331
Create an output menu item of type output and set the value of properties ObjectType
to SSRSReport and Object to the report created in the example above,
BankCardQueryBasedReport. Right click on the report and click on Deploy Element to
deploy the report. Open the report using the menu item. You will see the report as
shown in the following image:
You can also view the same report in Internet Explorer in your deployment location
which looks in below image. You can find the URL of the report in Reporting Services
Configuration of your SQL Server in Program Files or in the Report server configuration
discussed in Administration > Setup. To open the report after opening URL, click on the
folder and the report you deployed, BankCardQueryBasedReport.Report here is,
BankCardQueryBasedReport:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 332
You can even deploy the report from Visual Studio. Right click on solution and click on
Deploy Solution.
You can add ranges to the query data source to add the extra filters. Once you add the
ranges, you will see more ranges available in the report dialog. You can give the values
to open report filtered. You can also add parameters in the SSRS report in Visual Studio
while developing the report.
In the above screen, you can see two ranges added one in Query and the other in
Parameters. Adding ranges in query is known to you. Adding parameters is simple, just
add a parameter and set the properties as shown in the image below:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 333
Now that you are done with very basic reporting. Its time to jump into few more
advanced topics that will make you confident working on any report. Please note that
you can add multiple Datasets and multiple data sources into queries to play on the
report. You can try few exercises on reports from past tables to get good experience on
reports.
Consider a scenario where you need to edit an existing report. In that case, you dont need to
find where the project is in your file system. Instead all the projects you attach into AOT will be attached
to Dynamics AX Model Projects node. Just find the project of the report you like to edit, right click and
click on Edit. The following image shows this:
What should be done after editing the report? Whenever you edit the report, better check for
compile errors and build the report to check if you may get any issues. If you dont get any problem and
the report works fine, Save and add the report to AOT. Now, come back to AOT and deploy the report to
reflect the changes. If you dont deploy the report after editing the report, you may get unwanted
results i.e. the new changes/updates will not take effect. This will make sure your new version of report
deployed into the Server and when you try to generate the report, you will get the newer version which
has already overridden the older one. In this way, you can edit the existing reports.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 334
You may sometimes encounter cases where you need to populate the records from multiple
tables that may need many conditions and large calculations. In that case, simple queries may not work
and you may need the code support. AX supports these kinds of reports with framework support. This
will give you the flexibility to generate report for any complex requirement. Consider the following
scenario where you have few conditions to generate the report:
Sum the usage of credit amount of the customer in CardTrans.
If the sum is less than the limit, interest rate is 5% and if it crosses limit, interest rate is
10%.
In addition to the above calculations, you like to charge an amount of 100 for those who
didnt use the card and incentive bonus of 100 who didnt cross 25% of limit.
With these conditions and calculations, you may need support of X++ which will make your life
easier than writing complex queries or finding some other solution. To do this, the best option is, using
Report Data Provider classes. To generate a report using RDP class, you need the following elements:
AOT Query: This is the actual query used to fetch the data while working with class. You
specify the query using the attribute SRSReportQueryAttribute. This attribute specified
the class about the Query you use. You can use the methods like parmQuery () to get
the Query given in attribute.
Temporary Table: This table is used to populate and display the records. When you
create the report using the RDP class, the fields from this table are shown in dataset and
the records available in this table while executing the report are shown on the report.
You can have any number and type of fields in this table provided, it should be a
temporary table otherwise, the records will persists forever and you will get unexpected
results.
RDP Class: This is the actual class that will have the business logic [written by you] that
will process the report and populate the records into temporary table. The most
important method you override and use is, processReport (). This method is called on
this class which will have the code written by developer and can be used for the
purposes like filtering the data and populating the records into temporary table.
Follow the below steps to create the RDP based report. This example will discuss the SSRS
framework features like RDP classes, Data Contract classes and Controller classes gradually and close the
example. You can see most of the features used while developing a report in this example:
1. Create an AOT Query for BankCard and name it BankCard. This query will have only one
data source as per current requirement, BankCard. Other tables which might be needed
while developing the report will be taken in the method calls of RDP class.
2. Create a temporary table for the above scenario. This table should have all the fields you
like to display on the report. The following is the structure of the temporary table
created for current example which is a mandatory requirement. Please follow the
structure in the image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 335
3. Create a RDP class and name it BankCardTransDP. This class is the actual RDP class used
while you develop the report in Visual Studio while attaching the Query to the Dataset.
Following points should be considered while you code the EDP class:
a. The Query attribute should be specified in the class, above class declaration, using
SRSReportQueryAttribute e.g. SRSReportQueryAttribute (QueryStr (BankCard)). You
can get this query using the parmQuery () method at any given point of time in the
class methods.
b. Your RDP class should extend SRSReportDataProviderBase i.e. it should inherit from
SRSReportDataProviderBase class which will provide the functionality required by a
RDP class.
c. Override and implement the method public void processReport ().This method will
have the logic that is used to populate the temporary table and any validations or
calculations done as part of the business logic in RDP classs processReport()
method. The processReport method in RDP class is the point where the report
processing begins. The reporting framework will call this method as part of report
processing and actual report logic is written in this method. You can optionally add
an attribute SysEntryPointAttribute e.g. SysEntryPointAttribute (true), which will
have either true or false which is used to specify the class methods that require
authorization checks.
d. Implement a get method to return the record from temporary table. This method of
RDP class will select the temp table record and return it, which is used by the SSRS
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 336
report. The attribute SRSReportDataSetAttribute is used this in this method of RDP
class which specifies that the method in a SRSReportDataProvider object will be
used as a dataset in SSRS e.g. SRSReportDataSetAttribute('TmpBankCardTrans')
e. Finally, you can optionally add any number of methods as per your requirement.
The current class was given one more method called insertTmpBankCardTrans ()
which is used for inserting the records into temporary table.
f. Following is the RDP class implemented for this example:
/// <summary>
/// The <c>BankCardTransDP</c> class is the report data provider class for the
/// <c>BankCardTrans</c> SQL Server Reporting Services report.
/// </summary>
/// <remarks>
/// This is the Report Data Provider class for the <c>BankCardTrans</c> SSRS report.
/// </remarks>
[
SRSReportQueryAttribute (QueryStr (BankCard))
]
class BankCardTransDP extends SRSReportDataProviderBase
{
BankCard bankCard;
CardTrans cardTrans;
TmpBankCardTrans tmpBankCardTrans;
Query query;
}
/// <summary>
/// Fetches the data from the <c>TmpBankCardTrans</c> table.
/// </summary>
/// <returns>
/// The data from the <c>TmpBankCardTrans</c> table.
/// </returns>
[
SRSReportDataSetAttribute ('TmpBankCardTrans')
]
public TmpBankCardTrans getTmpBankCardTrans ()
{
select * from tmpBankCardTrans;
return tmpBankCardTrans;
}
/// <summary>
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 337
/// Processes the SSRS report business logic.
/// </summary>
/// <remarks>
/// Provides the ability to write the report business logic. This method will be called by SSRS at
/// runtime. The method should compute data and populate the data tables that will be
returned to SSRS.
/// </remarks>
[SysEntryPointAttribute (true)]
public void processReport ()
{
QueryRun queryRun;
// retrieve a handle to the base query
query = newQuery (this.parmQuery ());
queryRun = newQueryRun (query);
// fill the temp table objects with data from the base Query
while (queryRun.next ())
{
bankCard = queryRun.getNo (1);
select sum (AmountSpent) from cardTrans where cardTrans.CreditCardNumber ==
bankCard.CardNumber;
this.insertTmpBankCardTrans ();
}
}
/// <summary>
/// This method is used to insert the records into temporary table used by the RDP.
/// </summary>
/// <remarks>
/// This method is a custom method.
/// </remarks>
private void insertTmpBankCardTrans ()
{
tmpBankCardTrans.AccountNum = bankCard.AccountNum;
tmpBankCardTrans.TotalAmountSpent = cardTrans.AmountSpent;
tmpBankCardTrans.CardLimit = bankCard.CardLimit;
tmpBankCardTrans.CardNumber = bankCard.CardNumber;
tmpBankCardTrans.DateOfCreation = bankCard.DateOfCreation;
tmpBankCardTrans.Name = bankCard.Name;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 338
if (cardTrans.AmountSpent < bankCard.CardLimit)
{
tmpBankCardTrans.PercentOfInterest = 5;
}
else
{
tmpBankCardTrans.PercentOfInterest = 10;
}
if (cardTrans.AmountSpent <= 0)
{
tmpBankCardTrans.ExtraCharge = 100;
}
if ((cardTrans.AmountSpent/bankCard.CardLimit)*100<= 25&&
(cardTrans.AmountSpent/bankCard.CardLimit)*100>= 1)
{
tmpBankCardTrans.Incentive = 100;
}
tmpBankCardTrans.insert ();
}
4. Note that the method this.parmQuery () is used to get the query declared and used in the
attributes section of class declaration. In addition to this, you have few more methods which
will be used in coming examples. Compile the class and check if there are any errors, clear
the errors, save and compile the class.
5. Open the Visual Studio and create a Microsoft Dynamics AX Report Model project. Name
the project BankCardTrans. Add a report to the project and name that BankCardTrans.
6. Add a new Dataset and name it BankCardTransDP. Set the value of property Data Source
Type to Report Data Provider. Select Query, you will find the list of classes. Select the RDP
class just created i.e. BankCardTransDP and click Next and select all the fields from the
available list of fields. Please note that the list of fields populated are from temporary table,
which is shown in the following image. This image shows you the selection of Query and the
properties of dataset.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 339
7. Add a design to the report and update the Name and LayoutTemplate properties
accordingly.
8. Drag and drop the Dataset BankCardTransDP created in the above steps to the Design
created in last step. This will create a table layout on the report. Set the Style Template of
the table created in above step. You can optionally add the titles for the design or table you
created etc. wherever applicable. You can preview the report in Visual Studio at any time.
9. Save the report, build the report and add the report to AOT. Generate Incremental CIL from
Build menu of your development environment.
10. Create an output menu item and open your report. The properties of output menu item and
the dialog after opening the report using menu item are shown in the following image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 340
11. Notice that you dont have any ranges in the report. Click on Select to explicitly add the
ranges/filters to the query or click OK to generate the report. Your report should look as in
the following image:
12. You can optionally set few properties of the design and other elements to get good look and
feel of the report.
Consider a scenario where you need to display and accept the values with custom fields for the
report in the dialog and you may need to provide grouping and validation of the report parameters in
addition to accepting the parameters. To do this, you write one more class, called contract class. This
class is used to add custom dialog fields that needs to be shown on the report dialog which are not
parameters.
A contract class will have the parm methods which are used to accept custom fields on dialog
which can be used to filter the query or for any other purpose as per business logic. You can observe
various pre-defined contract classes in which one of the best example is, HcmADARequirementContract
which is used for ADA requirement report. This has only two parm methods that will accept values and
give to the RDP class. The following example is a data contract class for the BankCardTrans report
designed above:
/// <summary>
/// The <c>BankCardTransContract</c> class is the Common Data Contract class for all
/// <c>BankCardTrans</c> SRS reports.
/// </summary>
[
DataContractAttribute
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 341
]
public class BankCardTransContract
{
AccountNum accountNum;
CreditCardNumber cardNumber;
}
/// <summary>
/// Gets or sets the value of the datacontract parameter AccountNum.
/// </summary>
/// <param name="_accountNum">
/// The new value of the datacontract parameter AccountNum
/// </param>
/// <returns>
/// The current value of datacontract parameter AccountNum
/// </returns>
[
DataMemberAttribute ('AccountNum')
]
public AccountNum parmAccountNum(AccountNum _accountNum = accountNum)
{
accountNum = _accountNum;
return accountNum;
}
/// <summary>
/// Gets or sets the value of the datacontract parameter CardNumber.
/// </summary>
/// <param name="_cardNumber">
/// The new value of the datacontract parameter CardNumber
/// </param>
/// <returns>
/// The current value of datacontract parameter CardNumber
/// </returns>
[
DataMemberAttribute ('CardNumber')
]
public CreditCardNumber parmCardNumber (CreditCardNumber _cardNumber = cardNumber)
{
cardNumber = _cardNumber;
return cardNumber;
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 342
You can notice the following points from the above program. You can check them highlighted in
bold in the above program:
You have to use the attribute DataContractAttribute in class declaration of contract
class.
Notice that for each parm method, an attribute is used, DataMemberAttribute which
defines a data member used in service operations e.g. DataMemberAttribute
('AccountNum').
Coding the contract alone will not serve your purpose. This should be attached to the data
provider class. To do this, you have to specify an attribute named SRSReportParameterAttribute with
the class name of your data contract class e.g.
SRSReportParameterAttribute(classStr(BankCardTransDataProviderContract)). The following code
snippet shows the same:
[
SRSReportQueryAttribute(QueryStr(BankCard)),
SRSReportParameterAttribute(classStr(BankCardTransContract))
]
class BankCardTransDP extends SRSReportDataProviderBase
{
.
}
In addition to adding the contract class using the attribute, update your processReport() method
with the code to retrieve the values from contract class. In the current example, the account number
and card number are filtered that are entered by end user. The code of the RDP class modified is as
follows:
/// <summary>
/// Processes the SSRS report business logic.
/// </summary>
/// <remarks>
/// Provides the ability to write the report business logic. This method will be called by SSRS at
/// runtime. The method should compute data and populate the data tables that will be
returned to SSRS.
/// </remarks>
[SysEntryPointAttribute(true)]
public void processReport()
{
QueryRun queryRun;
BankCardTransContract contract;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 343
contract = this.parmDataContract() as BankCardTransContract;
if(contract)
{
accountNum = contract.parmAccountNum();
cardNumber = contract.parmCardNumber();
}
// retrieve a handle to the base query
query = new Query(this.parmQuery());
query.dataSourceNo(1).addRange(fieldNum(BankCard, accountNum)).value(accountNum);
query.dataSourceNo(1).addRange(fieldNum(BankCard, CardNumber)).value(cardNumber);
queryRun = new queryRun(Query);
// fill the temp table objects with data from the base Query
while(queryRun.next())
{
bankCard = queryRun.getNo(1);
select sum(AmountSpent) from cardTrans where cardTrans.CreditCardNumber ==
bankCard.CardNumber;
this.insertTmpBankCardTrans();
}
}
Notice the lines highlighted in bold, these are lines added which are used to get the contract
object and filter the records of query based on the values. It is not required that you have to use these
only for filtering but can be used for either showing or hiding the fields etc. based on the business
requirement. You can get the contract object using the parmDataContract() method of current object as
done like in this.parmDataContract() which returns the Object and can be converted to your contract
class object using as operator.
Once you are done updating the code, generate Incremental CIL and deploy the report to reflect
the changes. It is not mandatory to follow all the steps every time. You will get expected output even if
you skip few steps, but I recommend following this procedure to get rid of confusion. Once you are
done, open the report using the menu item, you will see the dialog with the dialog fields and on clicking
OK, you will see the report filtered as per the logic in processReport().
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 344
Notice the report in the second image, you are able to see only one record which is filtered in
processReport() with the code added .
You can use few more attributes in parm methods in contract class which can be used to set the
label and help text. The attributes SysOperationLabelAttribute and SysOperationHelpTextAttribute.
SysOperationLabelAttribute will set the label of the report parameter and
SysOperationHelpTextAttribute will set the help text for the report parameter.
[
DataMemberAttribute('AccountNum'),
SysOperationLabelAttribute(literalstr("@DAX1234")),
SysOperationHelpTextAttribute(literalstr("@DAX1235"))
]
public AccountNum parmAccountNum(AccountNum _accountNum = accountNum)
{
.
}
Note: @DAX1234 and @DAX1235 are the labels. These are displayed as text on the dialog or report. You
can check more about the labels in appendixes if confused and come back to the topic. You can check
HcmWorkerResumeContract contract class to check how grouping of fields is done.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 345
Finally, consider a scenario where you need to modify the report query before the report is run
or a case where you may need to modify the report query before the report dialog is rendered. To do
this, you need control before the report operation gets started. To do these kinds of operations, you are
provided with the controller classes by reporting framework in AX 2012. The controller classes give you
various facilities including the following:
In Model View Controller (MVC) pattern, this is the controller part of the report. The
controller does the following operations:
This will parse the RDL.
Get the report contracts that are used in the report.
If required and available create UI builders and invoke them.
Validation is done on contracts calling validate() method and this validation
occurs on the server through a service call.
Run the report on clicking OK.
Following are the cases when you may need to inherit and override the following
methods of controller class:
Update or change the contract or to change the query based on parameters,
you need this. To do this, override the method modifyReportContract() on the
controller class and give implementation.
You can override the events to react to form control events accordingly. You
have to override the method to do this.
You like to validate in addition to the validation exists in your report contract
and the validations that are not part of contract. To do this, override validate()
method.
You can use SrsReportDataContract.parmReportName() to change the name of
the report based on the parameters given in dialog and override the
modifyReportContract method to do this.
The following example shows sample implementation of the controller class for
the BankCardTrans report as follows:
/// <summary>
/// The <c>BankCardTransController</c> class is a controller class for the
<c>BankCardTrans</c>
/// report.
/// </summary>
class BankCardTransController extends SrsReportRunController
{
#define.ReportName('BankCardTrans.AutoDesign')
AccountNum accountNum;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 346
CreditCardNumber cardNumber;
}
public client static void main(Args _args)
{
BankCardTransController controller = new BankCardTransController();
controller.parmReportName(#ReportName);
controller.parmArgs(_args);
controller.startOperation();
}
/// <summary>
/// Override this method to change the report contract before you run the report.
/// </summary>
public void preRunModifyContract()
{
#define.parameterAccountNum('AccountNum')
#define.parameterCardNumber('CardNumber')
BankCardTransContractRDP bankCardTransContractRDP;
Query query;
BankCardTransContract contract = this.parmReportContract().parmRdpContract() as
BankCardTransContract;
accountNum = contract.parmAccountNum();
cardNumber = contract.parmCardNumber();
query = this.getFirstQuery();
// Modify the query contract based on accountNum & cardNumber.
SrsReportHelper::addParameterValueRangeToQuery(query, tableNum(BankCard),
fieldNum(BankCard, AccountNum), accountNum);
SrsReportHelper::addParameterValueRangeToQuery(query, tableNum(BankCard),
fieldNum(BankCard, CardNumber), cardNumber);
}
Following are few notable points can be observed from the above program:
The controller class should extend SrsReportRunController..
This class should have a main() which is attached to the name. Note that the name
passed is a combination of report name and design separated using a period, like
BankCardTrans.AutoDesign. You also pass the args and call startOperation() method of
the controller from main() which will start the actual controller operations.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 347
Notice the method preRunModifyContract(), this method is used to change the contract
before the report is run. This is automatically called and you can get the contract object
using the method call, as shown in the earlier example.
Note how the query is added the range values. You use SysReportHelper to do this with
the method addParameterValueRangeToQuery(). Now, update the
processReport()removing the filter in that method and check the output. You will get
the same as the query is added with filters in the controller. In addition to the
preRunModifyContract(), you have prePromptModifyContract(), which is used to
modify query based on caller args before report dialog is rendered.
Once you are done, as a better practice, compile, generate Incremental CIL and deploy
the report.
To use the controller class, create an Action menu item i.e. Menu Item > Action and
update the properties as shown in the image. You can check the Menu Items section
and come back for more information on menu items:
Compared to other SSRS reports, notice that you are creating an action menu item and
the controller is the class that calls and shows the report. You dont need any output
menu item and you call this class for showing the report.
Finally, a word about precision design is that, this design is used to place controls manually to
get the desired layout. In some cases like printing the registration form of an employee or an advance
note for vendors, you may need to generate custom designs where the auto design may not facilitate
the design that convenient. You can go for precision design in these situations. You can try the same
example to display in the form of labels or generate a note in letter format with the information that can
be sent to account holder. In addition to the above, you have UI builder classes which is used for layout
the fields on the contract and transfer data from a field to the contract, and bind fields to contract
members. The UI Builder classes used are SrsReportDataContractUIBuilder which provides the
capability to show the date effective tab and valid values that are specified in the report and
SysOperationAutomaticUIBuilder class provided by the SysOperation framework which will render the
UI for a given data contract. You can check samples in AX 2012 You can check the example
CustInvoiceUIBuilder for more information and an example on how to use the UI builder classes.
With enough information and exercises on Reporting covered with standard and SSRS reports,
its time to move to the next topic, Menu Items. Though the menu items are used throughout many of
the topics, this particular chapter covers complete information about the menu system in AX 2012.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 348
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 349
Menus and Menu Items
Till now, you have been developing various objects like Table, Classes, Forms, Reports, Visual
Studio projects, Macros, Jobs etc. in which few classes are self-executable which have main() method
and few are not. In all the objects, some objects are used by other objects internally and some are used
by the end user for doing regular business process. To make the objects available to end user, you
create menu items. You may ask why you need menu items if user can access the objects directly using
AOT. Answer is, every end user is not a developer and you dont give the accessing of development
environment to end users due to various reasons which includes security and misuse of the permissions.
Menus and Menu Items is a way of controlling the user to access the objects directly which can
be used to impose security at menu level and if you give accessing of development environment, it is
very hard for a user to identify the object and note that all the objects are visible in AOT for any user
with development permissions.
To call any object using a menu item, first a menu item should be created and later it should be
attached to some menu which can be then called. This two-step process is followed as you can create
multiple menu items for a single object due to various reasons like permissions, access levels and
purpose with different parameters which will serve different purpose and to specify the behavior to the
form based on the menu item used.
Not only the above reasons, few times you may include code to work based on caller and this
may be violated if users open the object directly from AOT. The following text explains the menu items
and how to use the menu items.
Menu Items
Menu Items refer to the object in AOT. Menu items are of 3 types namely, Display, Output and
Action. These 3 groups are logical groupings which display the relevant icon for the menu item used for
quick identification. The purpose of each group is as follows:
Display group should have the menu items that refers to forms. When you create the
menu item of this type, the property object type of menu item is set to Form and a form
name is selected as the value of the property Object for the menu item.
Output group will have the menu items that refers to reports. As the name suggests, this
will have the items that refer to the objects used for output, obviously reports. When
you create the menu item of this type, the property object type of menu item is set to
Report or SSRS Report and a report name is selected as the value of the property Object
for the menu item.
Action group will have the menu items that refers to self-executable modules or code
snippets like Classes or Jobs. Note that a class that has main() can be used for this menu
item as object and classes without main may throw the error Error executing code:
BankCardTransDataProvider object does not have method 'main'. if you try to access.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 350
When you create the menu item of this type, the property Object Type of menu item is
set to Class or Job and a class name or job name is selected as the value of the property
Object for the menu item.
You can create a menu item in one of the two ways described as follows:
First way is to drag and drop the object from AOT to appropriate group like forms in
display, reports in output and classes or jobs in action group under the Menu Items
node of AOT which will set the values for properties Name, Object Type and Object
accordingly which will reduce your time and effort. Though not mandatory you need to
set the properties Label and HelpText accordingly. If you dont set the value for property
label, you will see a weird label displayed in user workspace. This will be similar to
*Name of Menu Item*.
Other way is to create a menu item by right clicking on the appropriate group and set
the values for the properties Name, Label, HelpText, ObjectType and Object. Note that
you may need to specify the value for the property ReportDesign if you select object
type either report or SSRS report.
Though it is not mandatory, it is always recommended to proceed with your testing using the
menu items but not directly by opening objects to understand and analyze the results from end user
point of view.
Menu items are one of the sources of controlling the permissions and accessibility to users. You
have the properties Configuration Key and Security Key (in AX 2009) which can be used to specify the
accessibility of the menu item. In addition to these, you have some other properties like NeedsRecord
which will specify whether a record is required to use the menu item.
Creating menu item alone is not sufficient. If you like to display the menu item in the content
pane in addition to other menu items, you have to add this to a menu. The following screen shot shows
you the menu items in content area:
In the above image, All customers is a menu item of type display and the concerned menu item
name is CustTableListPage. To show your menu item like this, it should be added to the menu. The
following steps describe creating a menu item and adding to menu for the form created in the exercises
done in above chapters:
Find the form ZonesList created in chapter forms. This is the object for which you need
to create a menu item. To do this, either drag and drop the form onto the Display group
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 351
under Menu Items node of AOT or right click on the Display and click on New > Menu
Item.
Set the properties as shown in the following image and save:
Now, the menu item should be added to menu. Before doing that, you have to identify
the relevant module and menu that this menu item should be added. In current
example, Zones are defined to manage the inventory and trucks that should be
transferred to the customers. Here, keeping the use in point, the menu item will be
added to Inventory and Warehouse Management module.
To do this, find the module InventoryAndWarehouseManagement under Menus node of
AOT and expand it. You will find different sub nodes namely, Common, Journals,
Inquiries, Reports, Periodic and Setup.
Each of the logical group is used to identify certain functionality and the menu items are
grouped based on the action the objects referenced by them does. For example,
customers form and sales orders form of Accounts Receivable are placed in Common
section which is used regularly whereas customer group form is placed in Setup logical
group of Accounts Receivable as this is related to setup and is not done very frequently.
Similarly, the operations that are scheduled to be done periodically are kept under
Periodic and Journal related are kept in Journals.
It is better practice to keep the menu items under related group for easy identification.
Drag and drop the ZonesList display menu item created in above step onto the Common
node under InventoryAndWarehouseManagement node of Menus. This will create
menu item that refers to ZonesList menu item.
You can also create this by right clicking on the
Common logical group of Menus and click on
New > Menu item, set the properties accordingly.
You can find few interesting properties that can
be used to send parameters while opening the
object using the menu item or specify if the form
is displayed on content area. The menu item in
the AOT should look as shown in the image under
Common of
InventoryAndWarehouseManagement menu.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 352
Once you are done, click on save and for the changes to take effect, you have to close
the AX environment once and open it. You should see your menu item as in the below
image:
You can follow the above steps to create any number of menu items and attach them to
the menus as per requirement. Click on the menu item and observe the action done.
Consider a scenario where you are developing a new functionality that cannot be mapped to
existing menus. In that case, you can create a new menu and place your menu items related to that
functionality in your new menu. Consider an example, where the Zones related information is kept
under a new menu Zones. To do this follow the steps below:
Create a menu by right clicking on Menus > New Menu. Set the values for the properties
Name and Label to Zones. You can optionally check and set values for few other
properties.
Create a Submenu by right clicking on menu created in last step and click on New >
Submenu. This creates a sub menu, which is most probably used as logical grouping of
menu items. Set the properties Name and Label to Common.
Drag and drop the menu item ZonesList on the Common created in last step. This will
add the menu item in the Common of Zones menu.
Finally, this menu should be added to MainMenu to display when you try to list the
modules in Bread Crumb Bar. To do this, right click on MainMenu and click on New >
Menu reference.
You will see a list opened with all the menus available. Locate Zones [your menu] and
drag and drop on the MainMenu. This will add the menu reference to the MainMenu.
Position the menu in the list most probably alphabetical order is used and click on Save.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 353
Once everything is done, your AOT should look as shown in the below image. Note that
the image contains multiple screen shots.
Close and open the AX client for changes to reflect. You should see the menu available
in the list of modules as shown in the following image:
Following are few important properties of menus and menu items:
Properties of Menus
Label Set the name of the menu that is displayed to the user. If you dont
give label, you will get a weird label with *.
ConfigurationKey This sets the configuration key for the menu.
SecurityKey This will set the security key for the menu and is obsolete in AX 2012.
This works in AX 2009.
SetCompany This is a special feature, when set to Yes, every time user opens the
menu, the company changes to the company, when the menu was first
used i.e. the company info when the menu item was first used is saved
and will be used later.
NormalImage, ImageLocation,
DisabledImage,
DisabledImageLocation
Used to add an image and specify an image to use at different
situations like when menu is disabled or enabled.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 354
MenuItemType This will specify the type of the menu item, typically, Display, Output or
Action.
MenuItemName This will specify the menu item to include in the menu and depends on
menu item type.
Parameters This will specify the values passed to an object.
CountryRegionCodes This will specify country region codes where the menu is applicable.
This will help you show or hide the menus for country specific features.
ShortCut This will specify the keyboard shortcut used to open the menu.
Properties of Menu Items
Label Used to give a proper label. If no label is given, you will get the name in
between * which looks odd.
ObjectType Specify the type of object menu item will represent.
Object Specify the object that should be opened when this menu item is
clicked.
ReportDesign This is enabled only for reports. If you have multiple designs in your
report, this will be used exclusively to specify which design to use.
Parameters This will specify the arguments passed to the object.
EnumTypeParameter These properties will help you to pass the enum values as parameters
to the form while opening the form. You can change the behavior and
working of the form using these parameter values. An example will be
the LedgerJournalTable_XXXX display menu items where the same
form if used in different situations and the behavior is updated based
on the enum parameter value passed which you can see in the below
example screen shot:
EnumParameter
CreatePermissions,
CorrectPermissions,
DeletePermissions,
UpdatePermissions,
ReadPermissions
This property if used to specify whether the concerned menu item will
be available and able to select when privileges are assigned to the
menu item. Auto value will make the permission available and No will
make unavailable.
LinkedPermissionType Specifies the type of object pointed by LinkedPermissionObject.
LinkedPermissionObject This will specify the object of which the permissions should be applied
to the current menu item.
LinkedPermissionObjectChild This will specify the type of object pointed by LinkedPermissionObject.
RunOn This property will specify whether the menu item should be executed
on client, server or from where it is called. You can find more
information in tier selection to execute code/methods topic of this
book.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 355
ConfigurationKey This will set the configuration key for the menu item. Better to have
the configuration related to the module.
CountryConfigurationKey This is used in addition to the configuration key to enable or disable
regional specific settings.
WebConfigurationKey This is used to specify the web configuration key specific to web menu
items in addition to standard configuration key.
SecurityKey Used to specify the security key for this menu item [Can be used in AX
2009 and obsolete in AX 2012].
NeededAccessLevel This defines the least access required if the menu item should appear
on the menu or a button. To set access to menu items to different user
groups, this property is used.
ExtendedDataSecurity This will specify that the menu item will appear under all companies,
instead of in the context of a single company. This is a new feature
added in AX 2012.
Web Used only in AX 2009 and is used to specify the URL to open when
menu item is run.
OpenMode This is used to specify the view mode of the target form. Possible, one
of Auto, View, Edit or New.
CopyCallerQuery Specifies whether or not to copy the query of current object to target
object.
FormViewOption This will specify the form mode to use while opening. Default value is
Auto where the other options will be Grid and Details.
NeedsRecord This property will specify whether or not you need record to use this
menu item or not. For example, consider a case where you dont like
to enable the menu item button if there is no record, you can use this
to disable the button.
The following code shows you how to open an object using the menu item through X++ code
which will discuss various ways. The advantage of doing this is, the permissions are applied by default
which will be a great advantage. It is always recommended to open the object even through X++ code
using the menu items to get this advantage:
static void Job8 (Args _args)
{
FormRun formRun;
Args args;
MenuFunction menuFunction;
args = new Args ();
args.parm ("Called from Job");
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 356
//Used when no args is required.
new menuFunction (menuItemDisplayStr (ZonesList), MenuItemType::Display).run();
//Used when args is required.
new menuFunction (menuItemDisplayStr (ZonesList), MenuItemType::Display).run (args);
//Using with conditions.
if(true)
{
menuFunction = new menuFunction(menuItemDisplayStr(ZonesList),
MenuItemType::Display);
}
else
{
menuFunction = new menuFunction(menuItemDisplayStr(TruckTableMaster),
MenuItemType::Display);
}
menuFunction.run();
//Not recommended approach
menuFunction = new MenuFunction (menuitemDisplayStr (ZonesList),
MenuItemType::Display);
args = new Args();
formRun = menuFunction.create (args);
formRun.run();
//Finally, classFactory can be used
args = new Args(menuitemDisplayStr(ZonesList));
formRun = classFactory.formRunClass (args);
formRun.init ();
formRun.run ();
formRun.wait ();
}
//Used when no args is required: This is used when you dont need to pass any args to the object.
//Used when args is required: This model is used when you need to pass any args to the object.
//Using with conditions: This model is used if you have to instantiate/open different objects based on
some condition.
//Not recommended approach: This approach is not recommended due to a fact that this cannot
handle the situation if the menu item is changed to class.
//Finally, classFactory can be used: This approach can be used if you like to open the object directly.
You can also pass the object name directly to open for/report as shown below:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 357
args = new Args(formStr(<FormName>));
formRun = ClassFactory.formRunClass(args); // for forms
args = new Args(reportStr(<ReportName>));
reportRun = ClassFactory.reportRunClass(args); //for reports.
Note: It is not recommended to instantiate the application object directly as shown below to avoid
unexpected results.
formRun = new FormRun(...);
Locating the menu items of a form and from AOT:
Identifying Menu Item in AX is very easy and not a trivial task. This topic covers couple of
examples that shows you how to identify the menu item. Check the given image in which you have a
menu item called Free text invoice
under Sales update in Periodic
group of Accounts receivable. To find
about the object used and other
information regarding this menu item,
follow the steps below:
Expand Menus in AOT and
Periodic in
AccountsReceivable node.
Expand Periodic, find and
expand Sales update node,
you will find Free text
invoice.
Check the below image to
see the menu item found:
The above said method is used to find the action menus and
output menus especially. If you like to find the menu item used to
open the form, just right click on the form and click on personalize.
You will get a dialog. This dialog is usually used to do user level
changes particularly by end users based on their preferences.
This dialog will have three tabs namely Layout, Information
and Query. Layout will show you the design of your form and you can
drag and drop, change properties like visibility, enabled etc. This is
available to end users also. These modifications done by the end users
will store in Usage Data, which can be found under Tools\Options
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 358
menu.
You can find in the adjacent screen shot given,
using which you can check the usage data and clear the
usage data at times if you face any issues working with
multiple users specifically.
The other tab is, Information which will give you information about the object name, the menu
item name and caller information as shown in the following image:
Finally, Query will display the query used in this form. Please note that Query means not the
AOT query, whatever the data source you use is taken and treated as query as discussed in forms.
With enough information about menus and menu items, its time to move and see file handling
in AX 2012. This section covers handling all the types of files like Text, CSV, XML, Excel etc.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 359
Files
Files are used for basic storage and are the most common way to exchange data between two
or more systems. Though there are many advanced ways like Web Services, EDI [Electronic Data
Exchange], Messages etc. for exchanging the data, files has got its own place. Using files make life easier
few times and this can become a powerful tool for saving data, take a backup and restore especially in
few cases where you need only data of some tables. In few cases, you may exchange the data using file
system adapters and you may need to access files from the store, process them and place them into
archives. Following may be a scenario which requires file operations:
To take backup of a particular table and transfer into another environment or another
application.
To transfer particular set of data from one instance of AX to another or to another
legacy application.
Store a set of data into physical file.
The above are a sample uses of files. Files in AX can be used as a powerful tool in integrating
applications with AX. Following are a few types of file handling techniques shown in this text:
Text Files
CSV Files
XML Files
Excel Documents
Text Files
Text files are used to store plain and simple text. These are simplest way to store some
information and can be read directly without using any application from a simple Notepad application in
windows. You can use text files to store and transfer simple text or memo type values which are stored
in a field.
The most common class used to operate with text is TextIo. This class is an IO class and can be
used to write and read from text files in AX. The following example shows you how to write into a text
file and read from a text file. This program uses few other methods which will be discussed after the
program:
static void TextFiles_Write_Read (Args _args)
{
TextIo txIoRead, txIoWrite;
str textFilePath, textFileName = "AXTextFile.txt", textContent;
FileIOPermission fioPermission;
container containerToRead;
textFilePath = WINAPI::getTempPath (); //Step 1
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 360
fioPermission = newFileIOPermission (textFilePath + textFileName,"RW"); //Step 2
fioPermission.assert ();
if (WINAPI::fileExists (textFilePath + textFileName)) //Step 3
{
WINAPI::deleteFile (textFilePath + textFileName);
}
txIoWrite = newTextIo (textFilePath + textFileName,"W"); //Step 4
txIoWrite.write ("This is a simple demonstration in AX to work with text files."); //Step 5
txIoWrite.write ("Write is used to write text into a simple text file.");
txIoWrite = null;
txIoRead = newTextIo (textFilePath + textFileName,"R"); //Step 6
containerToRead = txIoRead.read (); //Step 7
while (containerToRead) //Step 8
{
textContent = Global::con2StrUnlimited (containerToRead, " ");
info (textContent);
containerToRead = txIoRead.read ();
}
txIoRead = null;
WINAPI::deleteFile (textFilePath + textFileName); //Step 9
}
Check the flow of the program, the below text will explain you how the code works:
Before going to discuss about the program, lets discuss use of an interesting class, WinAPI.
WINAPI is a class that is most commonly used to play with file system like checking for files and
directories, create and get permissions for files and directories etc. You can also use this class for finding
the disk space etc. The current example uses WinAPI. In the current example, we need to check for the
existence of files, and create a file in temporary directory. So, the WINAPI is used. In addition to the
methods shown in this example, you have many other methods which can be used for various purposes.
You can check them in WinAPI class in Class node of AOT.
Step 1: As per requirement, this is just a file that will be used for time being, so planned to
create in temporary folder and getting the path of temp folder.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 361
Step 2: Before doing file operations, its a better practice to get the permissions to do IO
operations on files. To do this, a class called FileIOPermission is used. The FileIOPermission class is
designed to check permissions for specific APIs. You should call assert () method which will call
corresponding CodeAccessPermission::demand method before the protected API is executed. The
FileIOPermission class controls the ability to access files and folders. Once your file operations are done,
you can revert the permission using CodeAccessPermission::revertAssert (), though it is not mandatory,
but a better practice.
Step 3: Check for the existence of file and if existing, delete the file. This is because, you may not
like to override or you may not have permissions. Please note that the current example is not using any
exception handling technique. It is always recommended to use exception handling techniques while
working with files.
Step 4: Just check how the text file is created using TextIO class. The parameters passed are a
file name and mode of operation. W is used for writing and R is used for reading. The constructor opens
the file based on the operation mode you suggest.
Step 5: Write () method of TextIO will write simple text into the text file. In addition to this, you
have other write methods which can be used to write the data into text file. Call this method as many
times as you like to write strings data into the file. Once the writing is done, null is assigned to the object
to close the file.
Step 6: Check the Step 6 in which the file is opened for reading. Please note that it is not
mandatory to write and read from a single program and this is just an example to demonstrate both
ways. Though you can use a single object for both purposes, two objects are taken here to avoid
confusion.
Step 7: The read method of the TextIO object is used to read from the text file. This will read
and return in the form of container which will have the string in the form of tokens. You can join the
tokens and use, or use as it is.
Step 8: This step loops and reads the lines one by one at a time and return the values read. Once
reading from file is done, it is converted to text and displayed on the screen in the next step using the
info and a method in Global class.
Step 9: Once everything is done, this step will delete the file.
As you can see from the above example, playing with text files is very simple in AX 2012 with the
support of most robust classes for handling files and file system. As text files are the very basic type of
files that are used to store data, you may need more complex ways to handle the data. To do the file
handling, you have few other ways. One of them is CSV. CSV files are comma separated files and are
mostly used to store data of one or more tables into the file system for backup or any other integration
purposes. These are the most common way of managing data in AX. The class used to work with CSV
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 362
files are CommaTextIO. This class can be used to do both write and read operations on the comma
separated files. The following example demonstrates how to write into a CSV file from a database table:
static void CSVFile_Write (Args _args)
{
CommaTextIO commaIOWrite;
FileIOPermission fioWrite;
BankCard bankCard;
commaIOWrite = new CommaTextIO (@"C:\BankCard.csv","w"); //Step 1
commaIOWrite.outFieldDelimiter (","); //Step 2
commaIOWrite.outRecordDelimiter ("\r\n"); //Step 3
fioWrite = new FileIOPermission (@"C:\BankCard.csv","w");
fioWrite.assert();
if(commaIOWrite)
{
while select bankCard //Step 4
{
commaIOWrite.write(bankCard.AccountNum, //Step 5
bankCard.CardNumber,
bankCard.AmountAvailable,
bankCard.CardLimit,
bankCard.DateOfCreation);
}
}
}
Step 1: CommaTextIO is used to open the file using its constructor. You can use the file modes
W for writing and R for reading from the file.
Step 2: outFieldDelimiter() is used to specify the field delimiter [This is the character that is used
to separate the fields] and is specified by using this method. As the file is a comma separated, usually, ,
is used. You can optionally give any character you feel comfortable and like to use as delimiter like ;.
When you pass multiple values to the write() method, this delimiter is used in between each field.
Step 3: outRecordDelimiter() is used to specify the delimiter to separate the records. Usually a
new line character or carriage return is used for this. You can optionally try others. Please note that
whenever you call the method write() of the CommaTextIO object, it will use this delimiter for division of
records.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 363
Step 4: Selection of the table is done for all records in this step. It is not mandatory to use this
kind of notation. You can choose based on your own requirement.
Step 5: write() is used to write the records one by one to the CSV file. One time insert will write
multiple field values into the CSV file and each field value is separated by field delimiter and record is
separated by record delimiter.
You can open the CSV file in any text editor or Microsoft Excel and check for the records inserted
into the file. With a write operation done, its time to read from the file. The below example shows you
how to read from the CSV file which will read from the CSV file and create the records in the database
table:
static void CSVFile_Read(Args _args)
{
CommaTextIO commaIORead;
FileIOPermission fioRead;
BankCard bankCard;
Container readCSVFileCon;
if(!WinAPI::fileExists(@'C:\BankCard.csv')) //Step 1
{
throw error("File doesnt exist");
}
fioRead = new FileIOPermission(@'C:\BankCard.csv', "r");
fioRead.assert();
commaIORead = new CommaTextIO(@'C:\BankCard.csv', "r"); //Step 2
commaIORead.inFieldDelimiter(","); //Step 3
commaIORead.inRecordDelimiter("\r\n"); //Step 4
if(commaIORead)
{
While(commaIORead.status() == IO_Status::OK) //Step 5
{
readCSVFileCon = commaIORead.read(); //Step 6
if(conlen(readCSVFileCon)>0)
{
bankCard.AccountNum = conpeek(readCSVFileCon, 1);
bankCard.CardNumber = conpeek(readCSVFileCon, 2);
bankCard.AmountAvailable = conpeek(readCSVFileCon, 3);
bankCard.CardLimit = conpeek(readCSVFileCon, 4);
bankCard.DateOfCreation = conpeek(readCSVFileCon, 5);
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 364
bankCard.insert(); //Step 7
}
}
}
}
CommaTextIO is the class used for reading from the comma separated version files. The file is
opened in read mode which will make you read the file as explained in steps below:
Step 1: This step will check for the existence of file and if file is not available in file system, throw
error that the file is not found and if exists, assert for the permission to use the file.
Step 2: Open the file in read mode. Now that the mode of opening the file is R which will open
the file in read mode and return the object.
Step 3: This will specify the delimiter used to separate the each field values. As comma , is
given in the last example while writing into file, the same is used here also.
Step 4: inRecordDelimiter() is used to specify the characters to use, which are used to separate
and identify the records. This will determine in the class what character(s) to search and verify whether
a full record has been read or not. Note that out delimiter methods are used while writing into the file
and in delimiter methods are used while reading from file.
Step 5, 6 and 7: These steps do check for the status of the object whether this is OK or not and
read the records one by one using the read() method of the object. Note that the read() returns the
container object which is used to store multiple field values and is retrieved one by one and finally, an
insert is done on the table.
With an example on how to read and write in CSV files, you can optionally check few other
methods and delimiters to make the best use of this class. The most common format used in AX to
exchange data is CSV.
Though you have any number of formats existing to write into files and exchange data, one type
has got its own identification and its own mark, XML. XML is eXtensible Markup Language which stores
the data using the tags.XML is a simple, very flexible text format which is widely accepted standard for
exchanging data from one application to another using web calls, often called as web services. This
format is also used to store some data for temporary purposes or store configuration information. With
the great advantage of XML, the following couple of programs let you know how to use the XML classes
in AX. These two programs will make you understand how to create and read the xml files. You can
instead prepare an xml string and send across different applications. The below is an example xml
document used by the programs that will be discussed in this text which will be created and read using
X++ code:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 365
Below program demonstrate how to create an XML file and write the records of a table into it:
static void XMLFileWrite(Args _args)
{
XMLTextWriter xmlWriter;
BankCard bankCard;
xmlWriter = XMLTextWriter::newFile(@'C:\BankCard.xml'); //Step 1
xmlWriter.writeStartDocument(); //Step 2
xmlWriter.writeStartElement("Details"); //Step 3
while select bankCard
{
xmlWriter.writeStartElement("CustomerDetails");
xmlWriter.writeElementString("AccountNo", bankCard.AccountNum); //Step4
xmlWriter.writeElementString("DateOfCreation", date2str(bankCard.DateOfCreation, 213,
2, 3, 2, 3, 4));
xmlWriter.writeElementString("CardNumber", bankCard.CardNumber);
xmlWriter.writeElementString("AmountAvailable", num2str(bankCard.AmountAvailable, 4,
2, 1, 0));
xmlWriter.writeElementString("CardLimit", num2str(bankCard.CardLimit, 4, 2, 1, 0));
xmlWriter.writeEndElement(); //Step 5
}
xmlWriter.writeEndElement();
xmlWriter.writeEndDocument(); //Step 6
xmlWriter.close(); //Step 7
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 366
Step 1: XMLTextWriter is a standard X++ class used to create and write xml documents. This
class is used to open the XML file for writing the xml text into the file or string. The newFile() is used to
create a new XML file using XMLTextWriter. You can also use newXml() of the object to create an xml
string.
Step 2: As you can check in the image given above for xml you can see the first tag with version
information. This is called document element and is given to specify some important information about
the xml document. This statement is added using the writeStartDocument().
Step 3: If you check the image again, you can see tags <Details> and <CustomerDetails>. These
are called as elements. These will have attributes or child tags. You can add these tags to the xml
document using the writeStartElement(). Just pass the string to the method.
Step 4: Other tags you see with the actual data e.g. AccountNo, DateOfCreation etc. are created
using the writeElementString(). These are the tags that store actual data. You pass the name of the
element and data that should be added to this tag. The data is enclosed in between starting and ending
tag. You can see various type conversion methods in example as this method will accept only string as
name and value. You can add as many tags you need in xml document.
Step 5: Every start element should have an end element in XML and the end element is added
using writeEndElement() method. The latest start element is ended first and no arguments are passed. It
is mandatory to follow the conventions while working with XML to avoid unexpected results.
Step 6 and 7: Once everything is done, close the document using writeEndDocument() and close
the writer. This will close and save the xml content into the file.
Once you are done the file will be generated and you can check the file in the file system, which
will display in the above format as shown in the image. As you know how to write into xml file or xml
string, now, time to see how to read from xml file. The following program demonstrates this:
static void XMLFileRead(Args _args)
{
BankCard bankCard;
XMLDocument xmlDoc = XMLDocument::newFile(@'C:\BankCard.xml'); //Step 1
//XMLDocument xmlDoc = XMLDocument::newXml(xmlString);
int i, noOfTags;
str amt;
noOfTags = xmlDoc.getElementsByTagName('CustomerDetails').length(); //Step 2
//XML tag starts with index 0
for(i =0; i <noOfTags; i++)
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 367
bankCard.AccountNum = xmlDoc.getElementsByTagName("AccountNo").item(i).text();
//Step 3
bankCard.DateOfCreation =
str2Date(xmlDoc.getElementsByTagName("DateOfCreation").item(i).text(), 213);
bankCard.CardNumber = xmlDoc.getElementsByTagName("CardNumber").item(i).text();
bankCard.AmountAvailable =
str2num(xmlDoc.getElementsByTagName("AmountAvailable").item(i).text());
bankCard.CardLimit = str2num(xmlDoc.getElementsByTagName("CardLimit").item(i).text());
bankCard.insert(); //Step 4
}
}
Step 1: XmlDocument and XmlTextReader are the classes that can be used to read from the xml
files or xml strings. This program uses XmlDocument to do this. You can see the newFile() method
passed with xml file. You can check the next line with newXml() which will accept the string as
parameter.
Step 2: getElementsByTagName() will return XmlNodeList. This can be accepted into
XmlNodeList object. In this case, the object is not created, instead the methods are called directly. You
can create a reference of XmlNodeList and catch the object returned by getElementsByTagName().
Step 3: Once you are done with step 2, you can use the item() method of XmlNodeList to get the
XmlNode. You can either catch the object or get the text directly as did in above program. Note that
index of xml nodes start with 0. You can use repeat this step to retrieve the values from multiple
elements.
Step 4: The values are inserted into the table as records as you can see in above step. You can
use these values to do certain operations based on your requirement instead of inserting into table.
Finally, excel files can also be created using X++ code. You can use SysExcelWorksheetHelper,
SysExcelHelper and SysExcelWorksheet to achieve this. The following example demonstrate you how to
create an excel file for the records from a table. I leave the understanding of this file to you:
static void WriteToExcelFile(Args _args)
{
BankCard bankCard;
SysExcelWorksheetHelper worksheetHelper;
SysExcelHelper sysExcelHelper;
SysExcelWorksheet worksheet;
int currentRow = 1;
str worksheetName;
str fileName;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 368
sysExcelHelper = SysExcelHelper::construct();
sysExcelHelper.initialize();
worksheetName = "Bank Cards List";
worksheet = sysExcelHelper.addWorksheet(worksheetName);
worksheetHelper = SysExcelWorksheetHelper::construct(worksheet);
// Populate the header row with the appropriate field labels and format the columns
worksheetHelper.addColumnFromTableField(1, tablenum(BankCard), fieldnum(BankCard,
AccountNum));
worksheetHelper.addColumnFromTableField(2, tablenum(BankCard), fieldnum(BankCard,
CardNumber));
worksheetHelper.addColumnFromTableField(3, tablenum(BankCard), fieldnum(BankCard,
DateOfCreation));
while select bankCard
{
currentRow ++;
worksheetHelper.setCellValue(1, currentRow, BankCard.AccountNum);
worksheetHelper.setCellValue(2, currentRow, BankCard.CardNumber);
worksheetHelper.setCellValue(3, currentRow, BankCard.DateOfCreation);
}
worksheetHelper.autoFitColumns();
worksheetHelper.formatWorksheetTableStyle(sysExcelHelper.getOfficeVersion());
fileName = strfmt(@'C:\ExcelFileXPP%1', sysExcelHelper.getFileExtension());
sysExcelHelper.save(filename);
sysExcelHelper.launchExcel();
}
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 369
Frameworks
There are multiple types of frameworks provided in AX 2012 which serve various purposes.
Most of the frameworks are discussed throughout the book. This topic explicitly covers few others
namely, RunBase Framework, SysOperation Framework and Number Sequence Framework.
Runbase Framework:
Runbase framework is an approach to create processes and batch able jobs in more
standardized way. This can be used to create dialogs and the job that is created using this framework is
batchable. Consider a scenario where the bank needs to send emails to its customers every week
informing about their balance details checking minimum balance. You have two chances to do this,
either assign a consultant who will do particular operation in timely fashion or run a process that will
execute in timely fashion and do the operation as per your requirement. The first one will be an error
prone as manual operations are less reliable that automated. To automate this process, you are given
framework, called as Runbase framework.
The RunBase framework will provide you a standardized approach creating processes and batch
jobs in Microsoft Dynamics AX. Implemented by the Runbase application class, this framework provides
many useful features including Dialog, Batch execution with RunBaseBatch class, Query, Validate etc. To
simulate this scenario, add a column called Email in your BankAccounts table. This will store the email id
of each account holder.
The following program shows you a sample implementation of RunBaseBatch which
demonstrates above scenario:
//Class declaration. The class should extend RunBaseBatch class.
class SendMailBatch extends RunBaseBatch
{
// Packed variables. These are used to display on dialog.
AmountMST MinBalance;
// Dialog fields
DialogField dlgMinBalance;
//Fields list to save. These fields are saved in
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 370
//SysLastValue table.
#define.CurrentVersion(1)
#define.Version1(1)
#localmacro.CurrentList
MinBalance
#endmacro
}
//The method must be overloaded in situations where a class must be selectable for a
//batchjournal.
public boolean canGoBatchJournal()
{
return true;
}
//This is used to populate/add the fields to dialog.
public Object dialog()
{
DialogRunbase dialog = super();
#resAppl
dialog.addImage(#ImageSysTestProject);
dlgMinBalance = dialog.addFieldValue(extendedTypeStr(AmountMST), MinBalance);
return dialog;
}
//This will get the values from the dialog a used to populate into fields.
public boolean getFromDialog()
{
MinBalance = dlgMinBalance.value();
return super();
}
//This is used to initialize any variables.
public boolean init()
{
return true;
}
//Constructor
protected void new()
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 371
{
super();
}
//This will save the values in the fields into SysLastValue table.
public container pack()
{
return [#CurrentVersion,#CurrentList];
}
//This will get the last used values and populate in the fields.
public boolean unpack(container packedClass)
{
Version version = RunBase::getVersion(packedClass);
switch (version)
{
case #CurrentVersion:
[version,#CurrentList] = packedClass;
break;
default:
return false;
}
return true;
}
// Contains the code that does the actual job of the class.
public void run()
{
SysMailer sysMailer;
BankAccounts bankAccounts;
str mailMessage;
InteropPermission permission = new InteropPermission(InteropKind::ComInterop);
permission.assert();
try
{
sysMailer = new SysMailer();
bankAccounts = qr.getNo(1);
while select bankAccounts where bankAccounts.AmountAvailable >= MinBalance
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 372
{
mailMessage = strFmt("Dear Customer, your balance in the account is %1. Please call us
for further queries.",
bankAccounts.AmountAvailable);
sysMailer.quickSend("sender@daxsoftronics.com",
bankAccounts.EmailId,
"Account Balance",
mailMessage);
info("Notification email sent.");
}
}
catch (Exception::Error)
{
info("Error due to mail configuration. Check and run again.");
}
}
//Validation is done in this method
public boolean validate(Object _calledFrom = null)
{
if (MinBalance <= 0)
return checkFailed("Error! Invalid value in minimum balance field.");
return true;
}
//Static method used to get the constructor
server static SendMailBatch construct()
{
return new SendMailBatch();
}
// Here goes a description of the class
static ClassDescription description()
{
return "Send mail batch";
}
//main method of the class. This is entry point.
static void main(Args args)
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 373
SendMailBatch sendMailBatch;
sendMailBatch = SendMailBatch::construct();
if (sendMailBatch.prompt())
sendMailBatch.run();
}
As you can see from the above program, a batch class is extended from RunBaseBatch class. You
have one more class called RunBase which is the base class in the framework. All the variables used
throughout class are declared in Class Declaration section of the class. You can also have few local
variables. The basic use of the RunBaseBatch is to create and display a dialog and schedule the job to
run at specified intervals. Each method in the batch has its own purpose and use. These methods are
executed in a predefined sequence. As this class has main(), it is a self-executable class as you know.
Create an action menu item and run the class using the menu item or run the class directly, you can see
a dialog as shown in the following image:
Observe the image, there is an icon, a field named Amount and a tab, Batch. You can add
any number of fields as per your requirement. This is done in the method dialog(). Follow the procedure
how a field is added to add multiple fields. You will see how to configure the batch after understanding
how the methods behave. The following table describes you about each method and its use:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 374
Method
Description
protected void new()
This is constructor of the class. As this is declared
protected, this cannot be used. Instead,
construct() is used to create an object.
server static SendMailBatch construct()
This is used to create and get an object of the
batch class.
public boolean init()
This is used for any initializations that should be
done.
public Object dialog()
This method is used to add and display the fields
on the dialog displayed when the class is run.
public boolean getFromDialog()
This method is used to get the field values that are
added in controls by the user. These fields values
are then used by developer.
public boolean canGoBatchJournal()
The method must return true when the class is run
from a batchjournal. The method must be
overloaded in situations where a class must be
selectable for a batchjournal.
public container pack()
Serialize the current instance of Runbase class i.e.
store the values of the fields in persistent store,
SysLastValue table which can be used further.
public boolean unpack(container packedClass)
De-serialize the packed class parameter value in
the current instance of Runbase class i.e. get the
stored values of the fields from persistent store i.e.
SysLastValue table and populate in fields of dialog.
public boolean validate(Object _calledFrom = null)
This method is used for validation of the values of
fields.
static ClassDescription description()
This will return description which can be used to
identify the batch.
public void run()
This method will have the actual business logic
that should be executed when the batch is run.
This contains the code that does the actual job of
the class.
static void main(Args args) This is the entry point of the class.
In addition to the above discussed methods, there are a large number of other methods which
are not used most commonly. The methods discussed above are most common methods which are used
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 375
in regular basis. In addition to the above discussed methods, there are few interesting methods which
are used to display the query browser and can be used for the selection of values in Runbase dialog. The
methods are as follows:
public QueryRun queryRun(): This is used to get the query browser on the dialog. This
will specify the query that will be displayed on the dialog.
public boolean showQuerySelectButton(): This will return either true or false which will
specify whether or not the select button should be displayed on the dialog.
public boolean showQueryValues(): This will return either true or false which will specify
whether or not the query values should be shown on the dialog or not.
The following is an example dialog that is shown when the above three methods are overridden
with some actual values:
As shown in the above image, when you override the methods discussed above related to query,
you will get some query related selection. The code for the methods is as follows:
public QueryRun queryRun()
{
Query q = new Query();
QueryRun ret;
q.addDataSource(tableNum(BankAccounts));
q.dataSourceNo(1).addRange(fieldNum(BankAccounts, AccountNum));
ret = new QueryRun(q);
return ret;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 376
}
public boolean showQueryValues()
{
return true;
}
public boolean showQuerySelectButton()
{
return true;
}
Note: These methods are added to display the query browser and other information. You should write
the X++ code to get the values selected in query browser which can be used in your program.
The main order that is followed while you run this class is as follows:
main() is called initially. You can call init() from main() if you have any initializations to
be done.
Constructor is called from main(). This is used to create the instance of the batch class.
prompt() is called from main().
unpack() from prompt()
dialog() from prompt()
description() from dialog()
queryRun() from dialog()
All these methods are called before OK is clicked. Once you click on OK, the method
sequence is as in below steps.
getFromDialog() is called which is used to retrieve the values entered by user when
dialog is displayed.
validate() is called for validation.
pack() is called to serialize the instance.
run() method is called from main().
Finally, the following procedure explains you about how to batch or schedule the class you have
written. Configuring batch should be done based on requirement and based on frequency required.
More frequent execution of same batch which is not required is not an acceptable job as this will
consume resources of your environment. The following image shows you how to schedule the batch:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 377
In the above image, observe the check box Batch processing. This should be checked if you like
to batch this class. You can optionally select Batch group, which will have few advantages of grouping.
Finally, Recurrence button will open a dialog which is used to schedule actual job and specify the
interval. The following image shows the dialog:
Just check various fields that are available on the dialog, which are used to schedule the process.
This dialog is used to specify the frequency or the interval at which the job should run. Configure the
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 378
batch using the above parameters deciding the frequency as per your requirement and click OK. This will
execute your batch and will generate notifications. You can check the notifications in the status bar of
the developer or user workspace. You can also check the batch jobs from System Administration >
Inquiries > Batch Jobs > Batch jobs [This will open the screen displaying the batch jobs scheduled] or
Batch job history [This will display the batch jobs history]. The screen shot of batch jobs is as follows:
As you can see in the above image, the list of batch jobs are displayed which you can update as
per your requirement. Please note that the menu items and location may differ in AX 2009. Schedule
and check how the batch runs for better understanding of batch processing in AX. You can check the
logs and history of the batch job using the buttons in the bar above grid and you can also update status
using the Functions button which can be seen in the above image.
The above batch program example follows
MVC pattern if you closely observe as shown in the
image.
The Model part of the batch is done using the
variables of the class. These fields are the dialog fields
and variables used to store dialog field values. These
will be used to display the list of fields on dialog. View
part is done using the methods dialog() and
getFromDialog(). These methods are used to render
the dialog and get the values of fields from dialog.
This is the UI part of the batch.
Finally, the controller part of the batch is
taken by the run() method which will have the actual
logic that will do the business operation when the
RunBaseBatch Service
Model
Members of class
View
getFromDialog() method of class
dialog() method of class
Controller
run() method of class
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 379
batch is run. This method is executed at the scheduled time and you should write the code in this
method.
As you can see, though MVC model is followed by batch, it is an internal architecture of a single
class and is not as flexible as SSRS. The following are the few more notable points about the
RunBaseBatch:
Runbase uses RPC model communication between client and server for communication.
MVC architecture is implemented and wrapped into one class.
Batch can be run directly without scheduling, or can be run scheduling i.e. can be run
only in one of the two modes, Synchronously in client and Asynchronously in batch.
With enough information on how to work with Runbase framework, its time to move to
advanced business operation framework, SysOperationFramework.
SysOperationFramework:
The SysOperationFramework is introduced in Microsoft Dynamics AX 2012 which is very robust
and preferred way to create the batch jobs in AX 2012. SysOperationFramework covers all the
functionality provided by RunBaseBatch and additionally, there are a lot of new features introduced in
the new framework. SysOperationFramework formerly called as BusinessOperationFramework is used
to develop enhanced batch process jobs in AX 2012. Compared with RunBaseBatch, this framework has
got great advantages in which the major one is a fully separated MVC components. The following image
explains about this in more detail:
Compared with RunBaseBatch,
SysOperationFramework model uses multiple
components/classes to design and deploy the Model,
View and Controller for creation of the batch
processing service. Each component in MVC
architecture have their own classes and are
developed as separate components which makes the
job robust and easy to manage or update the batch.
The model in SysOperation Framework is
handled by a contract class, view is handled by UI
Builder class and controller part is handled by
controller class. It is not mandatory to write few
classes until you dont have enough requirement to
use those components.
You can use the SysOperation framework to enhance processing performance by explicitly
specifying when and how operations are executed. These can run operations that are triggered by user
interaction, or schedule them to run via the batch server. The SysOperationFramework leverage the
SysOperation Framework Service
Model
Data contract class
View
UI Builder class
Controller
Controller class
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 380
batch framework by using multithreading which run in parallel to achieve maximum utilization of
resources and get maximum throughput. Following are the features supported and implemented by
SysOperationFramework:
Provide support for Queries.
Support parameter serialization using pack and unpack.
Support for routing tasks to the .NET Common Language Runtime (CLR) environment.
Using multithreading to build operations that scale to multiple processors by using
batch processing.
Allow support to prompt the user for input only on the client tier.
One more advantage of using SysOperationFramework is, you can expose these as services
which can be called from other applications. Services are more covered in Integration part of the coming
text which will expose the topic to its best. The following table demonstrates classes and their use while
you develop using SysOperationFramework:
Class Description
Contract class This class is used to provide implementation of the model of
the SysOperationFramework. This will have the parm methods
with few attributes used as per the contract object rules.
Service class This is any simple class which will expose the service operation
(a method with business logic to execute) like run() in the
RunBaseBatch framework. You can use any method of any
class and it is not mandatory to override or use only run().
SysOperationServiceController This class is used for designing the controller part of the
Framework. You can use this class directly or can override this
class based on requirement.
SysOperationValidatable This class is extended by contract to provide validation
functionality and put validation code on data contract. You
override public boolean validate() method to achieve this.
SysOperationInitializable This class is overridden on data contract class to provide
default initialization functionality. You override the method
public void initialize() to achieve this.
SysOperationAutomaticUIBuilder This class is used to provide custom UI for the framework. You
can use this class to provide the functionality like getting
lookup, handling dialog field events etc. on the dialog.
SysOperationServiceBase While working with threading environment using
SysOperationFramework, you may sometimes, need to find
whether the code is running in batch or not which may need
few methods to work with batch headers. This class will
provide the functionality to do this.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 381
The following are the list of advantages using SysOperationFramework over RunBaseBatch:
Automatic UI generation and custom UI generation.
Multithreading
Complete usage of MVC model
Able to expose as services.
With enough information about the framework, its time to move to a simple sample followed
by a more advanced one. The following example demonstrates sending mails to bank customers as done
in last example of RunBaseBatch. Below is the example of contract class:
[DataContractAttribute]
class BankCardDataContract
{
AmountMST amountAvailable;
AmountMST cardLimit;
str packedQuery;
}
[DataMemberAttribute, SysOperationLabelAttribute('Amount Available')]
public AmountMST parmAmountAvailable(AmountMST _amountAvailable = amountAvailable)
{
amountAvailable = _amountAvailable;
return amountAvailable;
}
[DataMemberAttribute, SysOperationLabelAttribute('Card Limit')]
public AmountMST parmCardLimit(AmountMST _cardLimit = cardLimit)
{
cardLimit = _cardLimit;
return cardLimit;
}
[DataMemberAttribute, AifQueryTypeAttribute('_packedQuery', queryStr(BankCard))]
public str parmQuery(str _packedQuery = packedQuery)
{
packedQuery = _packedQuery;
return packedQuery;
}
public void setQuery(Query _query)
{
packedQuery = SysOperationHelper::base64Encode(_query.pack());
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 382
}
public Query getQuery()
{
return new Query(SysOperationHelper::base64Decode(packedQuery));
}
The above is a contract class that is used to plan the UI of the application. You can use the UI
builder also for generating the interface to the user. The class has an attribute [DataContractAttribute]
declared which will specify that the class is a data contract class. A data contract can be created in the
AOT by creating a new class, and by adding the DataContractAttribute attribute to the class declaration.
Each parm method will be displayed as a dialog field with few attributes added as you can see in the
program. The attributes are DataMemberAttribute and SysOperationLabelAttribute. These two specify
the framework that the method is a data member and the label is specified using the
SysOperationLabelAttribute. In addition to these, you can add few other attributes,
SysOperationGroupMemberAttribute('PQR') and SysOperationDisplayOrderAttribute('5'). These two are
used to add the member under particular group in dialog and the other will make the field display in
specified order. In addition to the parm methods, you have one more important method, parmQuery().
This method will display a select button and query values in the dialog. The AifQueryTypeAttribute
attribute above the parm method specifies that a member is a query. The other two methods are helper
methods which you can use while using the class.
With the contract class available, you have to write a service operation. The SysOperation
service needs a service operation to run. This operation is executed when the user click on OK button or
when the batch job is executed in timely fashion. This is the place where your actual business logic goes.
The following is a simple operation that will send emails to customers as did in batch example:
class SendMailToBankCustomers
{
}
/// <summary>
/// Contains the code that does the actual job of the class.
/// </summary>
public void sendMail(BankCardDataContract _bankCardDataContract)
{
SysMailer sysMailer;
BankCard bankCard;
str mailMessage;
QueryRun qr;
InteropPermission permission = new InteropPermission(InteropKind::ComInterop);
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 383
permission.assert();
qr = new QueryRun(_bankCardDataContract.getQuery());
sysMailer = new SysMailer();
while (qr.next())
{
try
{
bankCard = qr.getNo(1);
if(bankCard.AmountAvailable >= _bankCardDataContract.parmAmountAvailable())
{
info(strFmt("Mail sent to customer %1 successfully.", bankCard.AccountNum));
mailMessage = strFmt("Dear Customer, your balance in the account is %1. Please call us
for further queries.",
BankCard.AmountAvailable);
sysMailer.quickSend("sender@daxsoftronics.com",
BankCard.EmailId,
"Account Balance",
mailMessage);
}
}
catch (Exception::Error)
{
info("Error due to mail configuration. Check and run again.");
}
}
}
Note: You may need to configure your mail server before you execute the program or may face errors
while sending mails.
If you observe the above class, this is a simple class with a simple method that has some
business logic to send the emails to the customers that come under certain criteria. This is the great
advantage with the SysOperationFramework, using the method of any class as the service operation.
Note that the data contract is passed as argument to the service operation method. This is passed when
the method is called by SysOperation while processing the job. Rest of the code is similar to the previous
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 384
example. As in the above example, you can use the data contract object for getting the parm values that
are initialized in dialog.
If you compare with RunBaseBatch, there is no main() in the current example. You may wonder
how this can be executed using this current model. To execute the current program, create the action
menu item with following properties:
The property ObjectType to Class and
Object to
SysOperationServiceController. Note
that you are using the standard
controller class to do the job. Instead,
you can write a custom controller
which will be explained in coming text.
Parameter as your service operation class method. This will specify the SysOperation to call this
method. Note that the method is prefixed with the name of the class separated by a period. Finally, the
EnumTypeParameter to SysOperationExecutionMode. You can choose one more property,
EnumParameter which will specify the execution mode of the operation, which will be discussed in
coming text of the chapter. Once you are done, save and open using action menu item. You will see the
following dialog:
Once you click on OK selecting some values, the method set in the property parameters is called
and is executed. This contract can be accessed in the method as argument is passed as discussed in
above example. You can also use the Select button to set the values for the query ranges and can be
used for filtering query run object while used in service operation method. Click on OK and check the
output.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 385
In addition to the above requirement, you may sometimes need to initialize or validate the
values of the contract. To do this, you can implement the interfaces SysOperationValidatable and
SysOperationInitializable. The first one will have a method validate() which is called automatically and
validate the values. The class declaration and method are as follows:
[DataContractAttribute]
class BankCardDataContract implements SysOperationValidatable
{
AmountMST amountAvailable;
AmountMST cardLimit;
str packedQuery;
}
//Validate the contract values
public boolean validate()
{
boolean ret = true;
if(amountAvailable <= 0)
{
ret = checkFailed("The amount available value cannot be 0 or less");
}
return ret;
}
In the similar way, you can try to initialize() of SysOperationInitializable. The major advantage of
using this MVC architecture with different components is independence, where you can modify any
component in MVC independent of others. Consider a case where you may need to initialize and control
the contract before the dialog is rendered or override the methods of dialog fields, you use the custom
controller. The below example demonstrates a custom controller. To create a custom controller, you
create a class and extend SysOperationServiceController. This should have a constructor and an entry
point, main():
class BankCardOFController extends SysOperationServiceController
{
}
public void new(identifierName _className = "",
identifierName _methodName = "",
SysOperationExecutionMode _executionMode = 0)
{
super();
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 386
this.parmClassName(_className);
this.parmMethodName(_methodName);
this.parmExecutionMode(_executionMode);
}
public static BankCardOFController construct()
{
ClassName className;
MethodName runMethodName;
SysOperationExecutionMode execMode = SysOperationExecutionMode::Synchronous;
BankCardDataContract bankCardDataContract;
BankCardOFController bankCardOFController;
className = classStr(SendMailToBankCustomers);
runMethodName = methodStr(SendMailToBankCustomers, sendMail);
bankCardOFController = new BankCardOFController(className, runMethodName,
execMode);
bankCardDataContract = bankCardOFController.getDataContractObject() as
BankCardDataContract;
bankCardDataContract.parmCardLimit(5000);
bankCardOFController.parmDialogCaption("BankCardOFController SysOperation Batch");
return bankCardOFController;
}
public static void main(Args _args)
{
BankCardOFController controller;
controller = BankCardOFController::construct();
controller.startOperation();
}
The above example does the following:
Accept the service operation method and the execution mode in new() and set them for
execution. This is used if new() is used for creation of object. Overriding new() is a better
practice though if you dont use in your scenario.
construct() method has the actual code that will set the service operation method to call
in job, execution mode to be used and set the default values to the contract. Note, how
contract is obtained using the controller object, using
bankCardOFController.getDataContractObject() method call in current example. You
are able to see how to set the caption of the dialog also.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 387
Finally, main() which is entry point of the program. Check the method call
startOperation(). This method is responsible to start the actual operation. Controller is
run using the start operation call.
There are many operations possible using controllers in which some of them are:
o Show/Hide dialog.
o Change the caption.
o Show query values and select button.
o Specify whether batch should run or not.
o Specify the execution mode.
You can also set the query range values in controller and change/update the query.
SysOperationFramework allows you to specify the mode of execution. You can set the mode of
execution to be used in menu item which is used while executing the batch. There are four execution
modes supported by SysOperationFramework as follows:
Synchronous: This is similar to RunBaseBatch. When you run directly without batch, the
client will remain in not responding state till the operation is done.
Asynchronous: When you run the operation in this mode, client will be in responsive
state while the operation is in progress. To do this, the operation should be added to
AOT service group and deployed.
ReliableAsynchronous: This will make sure that the operation runs even if the client
session in which it was started is destroyed and hence reliable.
ScheduledBatch: This mode will schedule a batch job for SysOperation even if
BatchProcessing check box is not checked. You can also set recurrence in this mode
even if the check box is not checked.
The above modes can be used in appropriate situations whenever and wherever applicable to
achieve the best output.
Finally, you can also use the UI Builder to generate the user interface you need. This is used
explicitly when you like to generate your own custom UI. This can be used to set few properties of dialog
fields or override the methods of dialog fields like lookup() or modified() etc. The below example
demonstrates usage of the custom UI. Please note that a new check box is added to contract using the
parm method named parmEnableControls() which accepts and return boolean and is used to either
enable or disable other controls based on the checkbox value:
Contract method as below:
[DataMemberAttribute, SysOperationLabelAttribute('Enable fields')]
public boolean parmEnableControls(boolean _enabled = enabled)
{
enabled = _enabled;
return enabled;
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 388
}
UI Builder is as below:
public class BankCardOFUIBuilder extends SysOperationAutomaticUIBuilder
{
DialogField dialogFieldEnabled;
DialogField dialogFieldAmountAvailable;
DialogField dialogFieldCardLimit;
BankCardDataContract bankCardDataContract;
}
public boolean overrideEnableDialogFields(FormCheckBoxControl _checkBoxControl)
{
// Enable or disable the amount available and credit limit dialog field
dialogFieldAmountAvailable.enabled(_checkBoxControl.value());
dialogFieldCardLimit.enabled(_checkBoxControl.value());
return true;
}
public void postBuild()
{
super();
// Retrieve the data contract
bankCardDataContract = this.dataContractObject();
// Retrieve the dialog fields
dialogFieldAmountAvailable = this.bindInfo().getDialogField(bankCardDataContract,
methodstr(BankCardDataContract,
parmAmountAvailable));
dialogFieldCardLimit = this.bindInfo().getDialogField(bankCardDataContract,
methodstr(BankCardDataContract,
parmCardLimit));
dialogFieldEnabled = this.bindInfo().getDialogField(bankCardDataContract,
methodstr(BankCardDataContract,
parmEnableControls));
// Register override methods
dialogFieldEnabled.registerOverrideMethod(methodstr(FormCheckBoxControl, modified),
methodstr(BankCardOFUIBuilder, overrideEnableDialogFields), this);
// Call the override already once to support packed value to be sync immediately
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 389
this.overrideEnableDialogFields(dialogFieldEnabled.control());
}
Note: postBuild() method is called immediately after the dialog is created and is the best place to write
the logic for overriding methods etc. The bindInfo() will return the SysOperationUIBindInfo object which
will have the information related to the data members for which the dialog controls are bound to. This
can be used to get the dialog control specific to the member and can be further used as per your
requirement. I leave further study and understanding of the UI builder for SysOperation and example to
you.
With enough coverage on SysOperationFramework and its usage, its time to move to the next
advanced topic, Number Sequences which has its own significant use. Please note that as
SysOperationFramework is very huge you have few more features to take a look which may work in your
scenario.
Number Sequence Framework:
Number sequence is a readable alpha numeric digit used to identify a transaction or master data
record. Consider an example where you generate invoices for sales orders. You may need to identify
sales order individually or invoice using some unique identifier which can be called as reference. This
reference is a number sequence. For example, you can consider SO-0001 and INV-0040 as your
sequence for sales order or for invoice. This series can be incremented when you create one more
related document.
Number sequence framework in Microsoft Dynamics AX provides the facility to create and play
around with this series. The number sequences in Microsoft Dynamics AX are used to generate readable,
unique identifiers for master data records and transaction records that require identifiers. This master
data record or transaction record that requires an identifier is referred to as a reference. Microsoft
Dynamics AX number sequence framework is used to generate alphanumeric number sequences that
are used to identify transaction documents like invoices etc.
Number sequences can be setup in AX in one of the two ways, either by using wizard or by using
the number sequence form. This section covers both the approaches in detail. To set up all required
number sequences at the same time you use the Set up number sequences wizard and to create or
modify individual number sequences you use the Number sequences form. Before plan to configure
number sequences, you may need to decide the following things:
Which module the number sequence requires? If it is an existing module, you can
directly create number sequences as per your requirement and use them. If you need to
configure the number sequence for a new module that is not in AX, you may need to
create the parameters table and Number Sequence Module class as discussed below.
This example shows the module configuration names ZoneManagement which shows
the steps to create the required artifacts for your new module to implement number
sequence framework in your module:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 390
o Create a parameter table for your new module i.e. ZoneManagement module,
this table is named ZoneManagementParameters. The table should have at least
one field, Key field and a find method. You have to override two other methods,
delete and update methods and can be copied from other parameter tables like
as CustParameters, which will serve your purpose.
o To represent all of the number sequences of the module, create an enumerated
value for the module. To do this, add new element to the NumberSeqModule
base enum. For your current scenario, add ZoneManagement element to the
base enum. It is recommended to attach the configuration key to avoid misuse.
o Its time to create the new number sequence class which should extend the
NumberSeqApplicationModule class. For your current scenario, name the class
NumberSeqModuleZoneManagement.
o Now, add the numberSeqModule() method to the new number sequence class
which should return the element for your module from the NumberSeqModule
base enum i.e. ZoneManagement. Check the following code snippet for
example:
public NumberSeqModule numberSeqModule()
{
return NumberSeqModule::ZoneManagement;
}
o Now implement the numberSeqModule method for the parameters table. You
can copy this from some table like CustParameters and create a new form for
the parameters table. You can refer any other form like CustParameters to find
the implementation details.
o Now, the number sequence framework is ready for your new module.
Decide the reference EDT that should be used for the number sequence series you
create and use.
Once you identify the above, you can proceed to the further steps for creating your number
sequence. The below steps create a number sequence for Inventory Module. You can follow the same
steps to create in any module including your new module:
Identify the EDT you like to use as reference In the current example, as ZoneId should be
unique and should be identified, the current example uses ZoneId to create the number
sequence.
Once you identified the EDT, find the loadModule() method in your number sequence
module class or create the method of same type and write the below code highlighted
in bold. This code defines a number sequence that is used by the Inventory module to
provide the Zone id numbers:
protected void loadModule()
{
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 391
NumberSeqDatatype dataType = NumberSeqDatatype::construct();
..
..
// Setup ZoneId increment
datatype.parmDatatypeId(extendedTypeNum(ZoneId));
datatype.parmReferenceHelp(literalStr("Zone id demonstration."));
datatype.parmReferenceLabel(literalStr("ZoneId"));
datatype.parmWizardIsContinuous(false);
datatype.parmWizardIsManual(NoYes::No);
datatype.parmWizardIsChangeDownAllowed(NoYes::No);
datatype.parmWizardIsChangeUpAllowed(NoYes::No);
datatype.parmWizardLowest(1);
datatype.parmWizardHighest(50);
datatype.parmSortField(1);
datatype.addParameterType(NumberSeqParameterType::DataArea, true, false);
this.create(datatype);
}
The above code has few interesting methods which decide the lowest and highest
values of number sequence series, the data type and labels and whether the sequence is
continuous or not and few other properties.
Now, you have to load number sequence information of the module. To do so, you have
to create an instance of the number sequence module class and call the load() or
loadModule() of your number sequence module class. You can do this for the module in
which you created number sequence. As its Inventory here, the following code will do
this for the inventory. You can follow the same for your module, ZoneManagement:
static void LoadNumberSeqModule(Args _args)
{
NumberSeqModuleInventory n = new NumberSeqModuleInventory();
n.load();
}
Once you are done with all above steps, you can generate the number sequences using
one of the two ways, either using the wizard or using the form.
Follow the below steps to create using wizard:
o Go to Organization administration > Common > Number sequences > Number
sequences.
o Click on Generate, as you can see in the below image. This will open the wizard:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 392
o The wizard will guide you through the generation of number sequences. If there
are no new number sequences you will get a popup with the message and will
close the wizard. You use the wizard to generate all the number sequences at
once. Click on Next to proceed further. You will see the below screen:
o You can observe the number sequence generated for all the companies
configured in the environment. You can select the required ones and proceed.
This is used when you like to generate a different series and numbers should not
be shared among the companies. You can set the smallest and largest properties
and click on Next and Finish.
If you like to share the number sequence among the companies, you can use the
number sequence form and create a new number sequence. To do so, follow the below
steps:
o Go to Organization administration > Common > Number sequences > Number
sequences and click on New > Number sequence. This will open the form as
shown below. Full form is displayed in one screen shot for understanding. You
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 393
can check the boxed items which play significant role in configuring the number
sequence:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 394
o In the identification group, type the code and name you like to attach to the
number sequence you create.
o In the scope parameters, give the scope of number sequence. Shared makes
number sequence series generate numbers irrespective of company and legal
entity i.e. for example, if a number is generated for company 01, and when you
try to generate the number for company 02 for the same reference, you will get
the next number. If you like to generate the numbers specific to company, then
you should select the scope accordingly. You can find few different types of
scope. To get this functionality, you may need to change some code in
loadModule().
o Once you are done with scope, add the References. The following popup is
displayed when you click on Add in References group
o Once you are done and click OK, reference is added and you look the References
group like in the below screen shot:
o Finally, in General group, you can find Setup and Number allocation. Number
allocation specifies the properties like smallest, largest etc. and Setup will
specify about the usage of numbers. If you check the option to use a continuous
number sequence, the next available number from the sequence is used when a
transaction is entered. The system verifies that no numbers from the sequence
are lost and if a number is generated but the transaction fails, automatic
cleanup makes the number available again. This feature may need to call few
procedures and may cause some performance drawback.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 395
o You can also pre allocate numbers and note that, Continuous should not be
checked for pre allocation to work.
o Finally, click on Save and close the form. Your number sequence should get
displayed on the list page.
Once you are done with creation or number sequence, its time to add code to access
your new number sequence data types. To do so, you need to write code in your
parameter table with the method signature server public static
NumberSequenceReference numRefXXXX(). You can copy an existing method from any
parameter table or write yourself as shown. This code will return the number sequence
reference of that specific data type. Example below shows for ZoneId which is written in
InventParameters table or may write in ZoneManagementParameters table if available:
server public static NumberSequenceReference numRefZoneId()
{
return NumberSeqReference::findReference(extendedTypeNum(ZoneId));
}
You can also add the code in following way which will find the scope also:
client server static NumberSequenceReference numRefZoneId()
{
NumberSeqScope scope = NumberSeqScopeFactory::createDataAreaScope(curext());
return NumberSeqReference::findReference(extendedtypenum(ZoneId), scope);
}
With everything get and set, its time to go i.e. use the number sequence created in
above example in your application. The following code will show you how to generate
and display the number sequence and display in an infolog:
static void Job12(Args _args)
{
info(NumberSeq::newGetNum(InventParameters::numRefZoneId()).num());
}
You can find the best place to generate the sequence in code. For example, the location
may be form method if the record is created in form, or a table method if you may have
some other way to create the record in the tables like from a web service or Enterprise
Portals etc.
Note: When you use the number sequences on form, you may need to add form handler using
NumberSeqFormHandler to handle the cancellation or deletion of record.
With enough information on Number sequences, its time to move to an advanced topic, using
.NET classes. To do this, first, we will create a class and create DLL and use the same in AX.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 396
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 397
Using .NET Classes in AX
Classes in assemblies that are managed by the common language runtime (CLR) can be accessed
in X++ code. Not only the existing classes, you can also create new class libraries in .NET and use them
from AX. Though this facility if available in AX 2009 also, the enhanced features in AX 2012 provides you
more flexible way to do this. This feature of Microsoft Dynamics AX is called .NET interop from X++.
.NET interop is usually done when you need to access some classes or features that are not
available in AX or some advanced features of .NET which can enhance the application. .NET interop from
X++ is useful when you want your X++ code to access the functionalities in a CLR managed assembly.
This includes assemblies that are installed with the .NET Framework. The .NET interop from X++ feature
works in the direction from X++ calling into CLR managed assemblies. Please note that you need .NET
Visual Studio Tools before you like to use these services.
This topic discuss on how to create a new class in .NET and use that in AX. The class simply
exposes the employee functionality. The following sample explains you how to do this. You may need
basics of .NET to follow with this example:
Create a C#.Net project in Visual Studio 2010. To do this, open Visual Studio 2010 IDE
and click on New > Project > Select Class Library > Give name EmployeeLibrary and click
on OK. This will create a new class library project. You can optionally select the location
of the project.
Add the below code in the C# project you created in above step:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 398
using System;
namespace EmployeeLibrary
{
public class Employee
{
private int tEmployeeId;
private String tEmployeeName;
private double tEmployeeSalary;
public int employeeId
{
get { return tEmployeeId; }
set { tEmployeeId = value; }
}
public String employeeName
{
get { return tEmployeeName; }
set { tEmployeeName = value; }
}
public double employeeSalary
{
get { return tEmployeeSalary; }
set { tEmployeeSalary = value; }
}
public void setEmployee(int _tEmployeeId, String _tEmployeeName, double
_tEmployeeSalary)
{
employeeId = _tEmployeeId;
employeeName = _tEmployeeName;
employeeSalary = _tEmployeeSalary;
}
}
}
Once you are done, add the project to AOT. To do this, right click on project you created
and click on Add EmployeeLibrary to AOT. The following image shows you how to do
this:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 399
Now, set few properties of the C#
project as shown in the image.
Set the properties Deploy to
Client to Yes and Deploy to
Server to Yes. These will
deploy the project in AOT when
you deploy the C# application
which can be used you while
programming using X++.
Build the C# application and
deploy the application from Build
menu in C#. You should find the
C# project added under C Sharp
Projects node of Visual Studio
Project of AOT as shown in the
below image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 400
You can edit the project at any time by right clicking on the project and click on Edit. This
will open the project in Visual Studio IDE directly.
Now, its time to access the class from X++. You may need to restart AX client once
before you use the class library deployed. Write a job as in the below code:
static void UseCLRInterop(Args _args)
{
EmployeeLibrary.Employee employee = new EmployeeLibrary.Employee();
int employeeId;
str employeeName;
real employeeSalary;
employee.setEmployee(1001, "John", 2500);
employeeId = employee.get_employeeId();
employeeName = employee.get_employeeName();
employeeSalary = employee.get_employeeSalary();
info(strFmt("Employee Id is : %1", employeeId));
info(strFmt("Employee Name is : %1", employeeName));
info(strFmt("Employee Salary is : %1", employeeSalary));
}
Run and check the output, you will see an infolog with the values.
If you check the above program, object is created for the class using the namespace and class
separated by a period. You can call all the methods with a slight variation. Intellisense will list the
methods available in the object for you in X++.
If you have an existing DLL created by someone, you can add the same to References node
under AOT by right clicking Referenced node and click on Add reference. You will get a window from
which you can select one of the existing or browse file system and add the reference for the DLL and
start using.
There are few considerations while using the application. The following are few of them:
While using types CLR Interop, the types of .NET may slightly differ and can be casted
into internal types of AX 2012. You may need to first cast into internal types and use the
local variables or may get unexpected results.
You may need to get interop permission to access the code in some situations to use
CLR objects. The InteropPermission class serve your purpose as shown below:
InteropPermission permission = new InteropPermission(InteropKind::ClrInterop);
permission.assert();
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 401
While debugging, you may not see the values of internal variables for objects of CLR
type. This is a drawback and you may not feel comfortable in few situations.
The following code gets the exception raised in CLR class:
System.Exception ex;
str exc;
ex = CLRInterop::getLastException();
exc = ex.ToString();
info(exc);
To handle these exceptions, you need to catch the CLRError in exception handling in catch
statement, like catch(Exception::CLRError).
In the above way, you can access the CLR types. You can try for multiple examples to get more
understanding on how the stuff works.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 402
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 403
Appendixes
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 404
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 405
New improvements and CIL in AX 2012
Consider a small example where you need to run your executable code in different
environments like Windows, Linux, and UNIX etc. you have the following options:
Either write program in all the languages and compile, generate environment specific
code or
Write code once and be able to use that in any environment.
If you check both the options, no doubt, you like to go for the second option as developer work
will reduce and less error prone and more advantages. But, how thats possible?
The answer is, using an execution environment which can process the code delivered by
developer in any environment provided, execution engine should be available in that environment. The
concept behind CIL is the same.
Common Intermediate Language, formerly called as MSIL (Microsoft Intermediate Language) is
the byte-code language that, the just-in-time (JIT) compiler of the .NET Framework interprets i.e.
Common Intermediate Language is the intermediate code generated by Visual Studio environment for
the same purpose. The idea behind this is, the execution environment will run the CIL code which will
provide more security and control. The execution engine for CIL is called as Common Language Runtime
(CLR). It works more like interpreter and gives more control over the code. Though .NET CLR may not be
available for all the environments this is working model and usage of CIL.
Coming to the case of .NET languages, you may write code in any language but the code is finally
converted into CIL, which is the advantage as CLR dont need to understand multiple languages and
understanding CIL is sufficient.
Coming to the case of AX, normal X++ code in AX is understood and executed by AX Kernel. AX
2012 has got significant changes, X++ code can be compiled into CIL. The use of CIL is, you may not need
AX kernel to run X++ code due to a fact that when CIL is generated, CLR will understand and CLR will run
the X++ code instead of AX kernel. This will increase performance and interoperability between .NET and
AX. Following are the scenarios where CIL code is required and run:
Batch jobs
Application Integration Framework (AIF) and services
In the above scenarios, the code is run by server explicitly and execution path of code starts at
server. These scenarios runs as IL code instead of p-code. P-code is compiled X++ code which is executed
by AX kernel. Performance of IL compared with p-code is more which is greater advantage of generating
CIL.
You can generate CIL in either of two ways, one by generating full CIL from initialization check
list while installing and configuring AX for the first time usage or generate Incremental CIL, which is used
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 406
to generate CIL in timely fashion whenever you modify few objects. Full CIL generation is time
consuming and you may need more processor cycles and memory. Incremental CIL generates only for
the objects that are modified but you have to generate the full CIL once when you install AX 2012 for the
first time.
CIL is generated only for Types of X++ i.e. Tables, Classes and Enums are X++ types and CIL is
generated for table methods and class methods. When you particularly work with services framework,
you need to generate CIL as they are executed from server directly. If you dont generate the CIL for
these, you may end up with errors. Running code in CIL can be done by programmer in few scenarios to
achieve performance or better interoperability. Following line of code is used to do this:
<return value> = Global::runClassMethodIL
(<class name>, // Or use classStr function.
<method to execute>, // Or use staticMethodStr function.
<arguments to pass to method>
);
You can use the above method to execute your own class method as IL. Finally, debugging CIL
code is different. You may not hit the code directly placing the debugger breakpoint, instead, you need
to open the .XPP file in Visual Studio environment and attach AX server process to VS and enable the
breakpoint.
CIL is a great advancement in AX 2012. I hope, this quick introduction gives you what and how
CIL is used in addition to the advantages. With quick information about CIL, its time to understand few
more quick topics.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 407
Installation and configuration of AX 2012
Installation of AX is easy compared with any other ERP which will have a few steps and check list
to follow. You can download the installer guide, Installing Microsoft Dynamics AX 2012 Step By Step
from www.axaptaschool.com which will have complete installation that covers the following:
Single box installation
Multi box installation
Installing SQL Server
Installing Project Server
Installing and configuring SharePoint for Enterprise Portal.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 408
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 409
Using Debugger in AX
If you are a programmer, second question from you will be what are the facilities in debugger.
AX debugger is a tool that can be installed while you install AX. You can check the installation manual for
more details on installing AX 2012. The following screen shot explain you about the debugger:
The most commonly used shortcuts in debugger are:
Step into F11
Step over F10
Step out Shift + F11
Run to cursor Ctrl + F10
Run F5
Insert/Remove breakpoint F9
You can keep breakpoints at any place in code at any time and hit the code. The following are
uses of each window:
Breakpoints window: This window is used to find how many breakpoints are kept and the status
(enables or disabled) of breakpoint.
Call Stack window: This window shows you the stack trace called for the current method call. This will
show the methods that are stacked while executing the current method.
Output window: This window will show the information or the content displayed of output statements
like info.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 410
Watch window: This is used to add and find the values of variables for quick reference and usually used
to watch the value of variable in multiple iterations or executions of a code part.
Variables window: This will display the entire list of the variables in local scope and global scope. You
can also view the values of instance variables in this section of the window.
You can configure the basic debugger settings using Tools > Options which will open the
following window [Click on development index on left side]:
The above image shows large stuff, when you click on Compiler button, you will see the small
dialog displayed on top of the actual options window. As you can see there are multiple levels of
compilation which will show you different levels. The Debug mode will display when the debugger
should be activated, usually When Breakpoint is kept. Once you are done with settings, save and close
the form.
X++ code is run in different tiers which was discussed briefly in classes section. You can run the
code in either server or client tiers. When you configure debugger in the above way, this will hit only the
code that runs on client. You need to configure debugger to hit the code that runs on server. The
following text will explain you about configuration tool that comes with AX. There are 2 configuration
utilities available one for configuring the server and other for client. You can find these under
Administrative tools as shown in following image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 411
Based on the configuration you like to create, you can either select Server configuration or client
configuration. These configuration utilities are used to create and share the configuration files and
update the server and client configurations. The following is a server configuration screen:
To create a new configuration, you can click on Manage > Create Configuration. You can save
the configuration as file also. To enable debugger on server, click on Create configuration, name the
configuration, select Application Object Server Instance if there are multiple instances and configure
TCP/IP port. Once you are done with primary settings, check the Enable breakpoints to debug X++ code
running on this server and Enable global break points. These will enable debugging of code running on
server. The following screen shows you database connection settings:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 412
The database configuration makes you configure database that should be used by your AOS. You
can change them if you like to use some different database as per your requirement. Following is the
client configuration utility:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 413
You can create new configuration using Manage > Create configuration. You can create any
number of configurations and save the configuration in file also. You may need to give few settings like
Server name, Instance name, the TCP/IP port and WSDL port. To edit these, you need to click on Edit
button. You can see the Configuration Target which have 2 options available as shown in below image:
Local client is used for the regular client you use and Business Connector is used for configuring
the business connector configuration. Business connectors are out of scope of this book and can be
found in book on Integrations from DAX Softronics which will be released soon. You can find few other
options in Developer tab as shown in below image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 414
You have to check the boxes as shown in above image to enable the debugger in client mode.
You can also set the layer you need to open and work. Except USR and USP, you may need to get license
codes from Microsoft or its partners.
Note: If you have multiple configurations created, and you try to use Microsoft Dynamics AX 2012
shortcut on All Programs for opening AX client, the current active Configuration in AX Configuration
window as shown in the above screen is used. You can create files and share with users when you may
have multiple environments configured in your machine like one for development and one for testing
etc.
Once you configured these, you can start debugging the X++ code that execute on client and
server tiers.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 415
Debugging standard AX X++ code is very simple. You can open the code editor and see various
buttons as described in the following image and follow the instructions using the debugger explained in
this chapter:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 416
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 417
Label Files and Labels
Till now wherever you need text to be displayed or labels etc. to be created and displayed, hard
coded strings were created and used everywhere. Consider a case where you are developing an
application/product or some customizations which will be used by a multinational company. In that
case, your client may ask for labels in local language for each of their branches located in different
nations. If you hardcode in any language wherever and whenever you try to access application, you will
always get the labels in the language you hardcoded.
To resolve this problem, you should create the label file and labels using label file wizard and
label editor for the required languages in local language, which will get displayed based on the language
selected by the user in client configuration. This will give multi-language localization support in AX.
Current text explains you about the labels and its features in AX 2012.
Labels are used to specify the user interface text for forms and reports. Labels are created in the
Label Editor and used in text-based properties like Label, Help Text etc. in the Application Object Tree
(AOT) or in X++ code. A label can be 2000 characters long and can be created using Label wizard. The
label created by you for one object can be used in multiple places and can be used by multiple objects.
The following steps will guide you creating label file using the wizard:
Click on Tools > Wizards > Label file wizard in development workspace.
This is used to create a new label file. You will get a screen as shown in the below image.
You can create a new file ID or new label language based on requirement. Most
probably, you may need to create new label file id as a large number of languages are
supported by AX. You can use new label language option in very rare cases whenever
you required:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 418
Select the required option(s) in the dialog and click Next. You will get the screen as
shown in below image where you need to enter the ID for the label file:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 419
Once you type the required ID, you can click on Next and Finish in the coming screen.
There are few notable points while creating the label file as follows:
o Label files should not be given existing names of Layers or languages due to risk
of getting overwritten while upgrades or other operations are being done.
o When you create a label file, a set of ALC, ALD, ALI files are created for the
languages you use in your environment as shown in below image:
o These files can be copied and distributed when you do deployments of the
application customizations or modifications you did. The ALC file stores
application label comments, ALD file stores application label data and ALI file
stores application label index. You may find many other files for each layer and
for each language. It is recommended not to modify/update those files and
create a file according to your requirement.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 420
Create labels:
Once you are done with creating the label file you need, its time to create labels for all of your
requirements i.e. hard coded strings and the strings you need in further development. To do this, you
use the label editor. Label editor is a tool used to create and use labels for multiple languages in AX. The
current topic will explain you about using the label editor and using labels in code as well as for objects.
Check the following image:
In the above image, Label and Help Text are hard coded and you have to create labels for these
instead of hard coding the strings. To create a label for this, click on ellipsis () button next to the label
as shown in the below image:
This will open the label editor as in below image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 421
As there are no labels existing for this label, you are not able to see any existing labels. If there is
any label existing or already created, you will see the list of existing labels from which you can select one
as shown in below image:
Select one and click on Paste label which will paste that particular label into the label space of
the object property you selected. Click on Setup tab which will open the following screen:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 422
Select the Label file ID and languages for which you like to create the label. Note that you have
to select the Label file ID you created which will be distributed later with your code. You need to select
the languages for which you like to create label for. You may need to create labels for multiple
languages in few situations and you need the license for language to use or, you will see only the
language you have the license for. Most of the times, organizations will take license only for the
languages they are into and not for all the languages which will cost more for licensing. Once you are
done with selecting these, go to Label tab and click on New. You will see the below screen which will
allow you to create new label in your primary language:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 423
Give Label and Description for the label and Save using Ctrl + S or File -> Save. This will generate
a label id and will allow you to create labels for other languages. You can see the below screen shot for
reference which is allowing you to configure labels for the languages you selected:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 424
You can add the Label and Description for each Language and click on Save. Once you are done,
click on Paste label which will paste the label for the property you selected. You can check this in
below image:
Note: The labels of particular language are used based on the language you select for your AX client
environment. This will show all the text in your configured language. Language can be selected from
Tools > Options and select the language in General tab.
If you check the Label ID, this is common for all languages and AX environment will interpret the
label text to use based on Language you select.
It is recommended to use labels in all places including your X++ code to avoid the language
issues when end user uses the environment. To create a label that is hard coded in your X++ code,
follow the below steps:
Select the hard coded string you like to create label for.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 425
Right click on Lookup Label/Text. This will open the label editor as discussed in above
steps which is used to create a new label.
Follow the steps, create label and click on Paste label. This will replace the string with
the newly created label as shown in below screen shot:
Check how the label convention is, a label starts with @ and will have the label file id with actual
label id as an integer value. This is replaced by AX when you run the code with the actual label text. If
there is no label with the particular label id existing in AX, it will interpret the label id as plain text and
display the same which looks odd. So, you have to take care before using the labels and check whether
they are created or not and distribute the label files while you use the label ids in other environments.
With enough information on creating and using labels in AX, its time to move and look on some
basic administration which every AX developer needs. The following appendix discuss about the basic
administration needed and few interesting topics in administration part.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 426
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 427
Basic Administration
Any developer needs some basic administration knowledge of any technology. This section will
explain you about some basic administration of AX 2012 like exporting and importing code, exporting
and importing data, basic SQL administration from AX etc.
The coming text will explain you about each one clearly. Follow the steps once for every
requirement:
Take code backup and restore: This is very frequent requirement where you need to backup code or
take the code backup and restore into some other machine or some other environment. To do this,
follow the below steps:
Open the project you created (if you need only a single object, just follow the below
steps).
Right click on the project or the object you like to take the backup as shown in image.
Browse for the file location and give a good meaningful name as shown in below dialog:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 428
You have few options in the dialog like selection of the layer you like to export, sending
exported file in mail etc. you can check these for various options.
Once you are done, click on OK. This will generate an XPO file. Based on the size and
number of objects you selected, time of export may vary. This XPO file is a plain text file
which will have some code. You can open the file in any text editor and check the code
in the file.
Once the export is done, you can use the XPO file transferring your code updates to some other
environment. You need to send this file to the other environment and import the code modifications. To
do this, follow the below steps:
Open AOT and click on Import as shown in the following image:
This will pop up with a dialog requesting you for selection of XPO file and you will be
given few other options also as shown in below image:
You can check for few options and their uses. Once done, click on OK. This will import
and compile the imported code. Once everything is done, you can check the
modifications of the objects. They are updated with the new imports.
Note: As most of the time you work with models in AX 2012, you may need to export and import model
files. To do this, you can use the Axutil command from command line or you can use PowerShell for
these operations.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 429
Take data backup and restore: Data backup can be taken in two ways. One may be from SQL Server
directly using the backup option given in management studio for any database and the second will be
from AX. This section will explain you about the options from AX.
Go to System Administration module. In Data export/import, find the Export to and click
on that.
This will open the following form in which you can select the name of file and the type
etc. as shown in below screen shot:
You have to select the filename, definition group. Definition groups are used to identify
groups of tables that can be imported or exported when you use Microsoft Dynamics AX
data import/export and this should be created before you work with export data tool.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 430
You can also batch this if you like to take the backup at regular intervals. Once
everything is done, click on OK. This will export the backup into a .DAT file. You cannot
read Binary file. You have one more option, Comma which will generate a CSV file which
can be read by you in Excel or any text editor.
Once you are done with export, you can use the same DAT file to import data into some
company or some other environment. To do so, you need to click on Import in System Administration
module > Data export/import > Import. Select the file to import and click on OK. You may need to take
few decisions like overriding existing data in few cases if thats asked. Once everything is done, you can
open and check the forms for data. Based on size of the file, data import may take time.
Create Companies: If you have multiple entities or multiple companies that does different operation,
you may need to create multiple companies. AX supports this and has a slight difference between AX
2009 and AX 2012. In AX 2009, you used to have Company accounts in Administration and you can use
this to create multiple companies and select company you like to. In AX 2012, the organization structure
changed and implementation changed. You have various entities in AX 2012 based on increasing
organization requirements. The following screen shot shows you the available options in AX 2012:
The internal organizations can be created using one of Legal entities, Operating units, or Teams.
A Legal entity is a legally registered and has a legislated legal structure. You can create this as an
alternative to company account in AX 2009. To create one, click on Legal entities which will open the
form shown below:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 431
Click on New and give Name, Company and Country/region and click OK. This will create your
legal entity. You can give few other parameters like language, address etc. as seen in image in the above
screen shot. Once done, Save and Close the form. Close AX client once and open the same. You will find
your new entity created in the list. You can select the new entity created and do the operations like
configuring, data import etc. you need.
Create Users: The most common requirement is, create users and set permissions. This text covers the
basics of doing these operations. Creating users in AX 2012 can be done in one of the two ways. First is,
you can create a user directly or import users from Active Directory. To do this, click on Users in System
Administration under Common\Users group. You will see a list page that display the list of users
configured in AX as shown in the following image:
Click on New > User to create a new user manually. You may need to set values and properties
for the new user created, assign roles and save and close the form. Second way is, import from active
directory which is the easiest way. This will open a wizard which will guide you through the process. To
import users from Active directory, click on New > Import in the user list page as shown in above screen.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 432
This will open a wizard. Click on Next in the welcome screen which will open the screen as shown in
below image:
You can add more filters for filtering levels but most common will be Domain name. Click on
next to get the list of users. You may get a warning popup saying that it may take time to find the users,
you can continue or add more filters like Department, First name etc. which will filter the users list
quick. You will see the below screen once you click on Next.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 433
This has the list of users in Active Directory. You can select the users you like to import and click
on Next., which will show the following screen. Click on Next:
The following screen allows you to select roles for the users created in this wizard. There should
be one minimum role selected by you, System user to make that user as an AX user. You can select
other roles based on requirement. Roles allow a user to do set of operations. For example, System
administrator will make the user to act like administrator. You can always change/update these roles in
future whenever required.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 434
Once done, click on Next which will allow you to select profile for the users. You can choose
from the options available and click on Next and click on Finish in the final screen which will close the
wizard and show the newly created user in the list page as shown in below screen shot:
Now, you can use the user to open and work with AX. Without configuring the users, even an
Administrator in Active directory cannot access and do anything in AX.
SQL Administration: You can do some basic SQL Administration from AX like, Synchronize the database,
do re-indexing, generate scripts for creating or deleting tables etc. These can be done from SQL
Administration form under System Administration > Periodic > Database > SQL Administration. This will
open the below form:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 435
In the above form, you can find few buttons which will open related menu items. You can go for
Synchronize or do indexing from the menus available on top and even generate scripts. Synchronization
is a time consuming process and depends on the volume of data and processor power and memory
availability in your machine. Synchronize database or Check/Synchronize will Synchronize the table and
index information to the database.
Set report server parameters: The most common requirement while working with AX 2012 is, setting
report server parameters for SSRS reports. You can open the form from System Administration > Setup >
Business intelligence > Reporting Services > Report servers, which will open the form as shown in below
image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 436
You can do the following in this form:
Set the report server and the SQL Server information.
Set the report server URLs to manage the reports and the web service URL also. You will
get these while installing the reporting services.
Set the Report folder and you can create a new folder if required using Create report
folder menu.
Select the AOS server to use.
Once you are done with configuration, you can click on Validate settings once you are done with
configuration which will validate the settings and check if everything is fine or not. You can then start
deploying your reports once this is configured. Usually, this will be configured when you install the
reporting server extensions while installing AX 2012. You can modify these based on your requirement.
There will be a lot of administration tasks that can be done in AX 2012 in which the most
common requirements are mentioned above. With enough requirements of simple administration
required by developer, its time to move for working on some basic tools that can be used by a
developer in development environment. Coming section covers the basic tools required and used most
commonly by a developer.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 437
Interesting tools in AX development environment
Any development environment will have some tools which make you work easily and give few
extra facilities for development, debugging, finding or comparing the changes and other stuff. AX 2012
also has few tools that will provide you various facilities to work with. This section discuss about few
important and interesting tools given by AX development environment.
Find tool:
As the name indicates, this tool is used to find some text or the node with given name. Just right
click on a single object or some selected list of objects or a parent node and click on Find.
You will get a dialog in which you can type the text or name you like to find, and click on Find
now button. This tool will search the string or name you type and display the list in the below area as
shown in the below image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 438
You can always double click on the source code line to open the code and use accordingly. You
can also use the advanced filters to do some advanced searching also. This can be used to find some
existing or newly created or modified application objects by you and work accordingly. This will be the
most commonly used tool by a developer to search the required code snippet or some other stuff in
your project.
Compare tool:
Consider a case where you modified some object in
multiple layers and check the modifications done in some
particular layer compared with its upper or lower layer or
consider a case where you need to merge the code done in some
environment in different environment. In both the cases, you
need to compare the object in different layers or you may need
to compare the current layer with the XPO you like to import. In
both the cases, you need a tool for comparing the objects.
Compare tool can be used to compare an object in different
layers or with XPO. Just right click on any object that is modified
in multiple layers and click on compare or check Show details
check box and compare the object you like to import. The
following screen shot shows this. In the below image, you can
find the Show details checked and pointing to the required
object. You can also find the Compare button, which is active.
You may find the button disabled few times, if there is no object
with that name in AX:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 439
Click on Compare in either of the cases, you will find the layers to compare between and the
difference is shown in the right pane as shown in the below image:
You can select the node you like to compare in the left pane and check the differences in right
pane. You can either merge or remove modifications. The modifications are color coded for easy
identification. You can always change the selection of the layers and click on compare to get the
comparison between the layers selected.
Compare tool works great when you import and export code modifications and working with
multiple layers to fix the issues.
Code Profiler:
Code profiler is used to check the code lines executed from different object when you do some
processing in AX. The Code Profiler measures the execution time of individual lines of code which can be
used to find performance issues and to help understand code that was developed by others. You can use
this for identifying the method traces also. To use the code profiler, click on Code profiler in Tools and
click on Start.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 440
You can optionally set few check boxes like Profiling on server or trace depth and click on Start.
Once you are done with the operation you need, click on Stop. This will display a popup dialog to enter
name for the profile as shown in below image:
Give some good name for identification and click on OK. Once done, you can check the trace or
profiler runs using the button Profiler runs. You may need to wait for some time until the profiler logs
the trace and the time of execution. You can check the status in progress bar and once done, click on
Profiler runs which will open the profiler runs dialog. Select the run you like to check and click on a
button, Call tree which will display the actual trace to you as shown in the following image:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 441
As shown in the above image, you will get a large volume of trace information. Its better to use
trace for small operations or quick operations, as the preparation of trace will consume time and if you
do lot of operations, it may take long time for generation of the trace.
Debugger:
Debugger is the tool used to debug and find logical errors in code. Debugger is very powerful
tool and can be used all times. A full explanation for debugger is given in Debugger topic of Appendixes
where you can find handful information.
Number of records:
This tool will display list of all tables and the records in each table. You can check the count of
records in table which can be used for finding volume performance issues. You can find this tool in Tools
menu. This may consume some time based on the number of records in tables. The below image shows
you how this looks:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 442
You can also find some other tools that can be used in routine development practices or in rare
situations. With a good enough workout on tools, its time to get into some best practices. The next
section will discuss about the best practices and importance.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 443
Best Practices and their Requirement/Microsoft Conventions of
Programming in DAX
While working with the Microsoft Dynamics AX development environment, you should follow a
set of best practices. These best practices and programming practices are for increasing the
performance of the application and better understanding of development by any other developer in
future developments. The X++ compiler checks the code for best practice issues and will result in best
practice errors, warnings, or informational messages.
The code compiler tool will compile the code and find different levels of errors and warnings.
When you compile, compiler will generate 4 kinds of information. This will include Errors, Warnings,
Best practices and Tasks. The following image shows this:
Errors: These are critical errors which should be resolved to execute the program. They are usually the
syntax errors done by programmer.
Warnings: These are warnings and should be resolved possibly to avoid unwanted results few times. You
can run the program even if you dont resolve these, but it is better to clear the warnings to get best out
of your program and avoid unnecessary comments while compiling the application.
Best practices: These are best practice warnings which are shown after full compilation of the object
you created. It is always recommended to follow best practices and these should be resolved to get best
out of your program and for better understanding. These include the conventions to follow etc.
Task: These are also called as ToDos and usually indicate that you have to update some code in that
place. These comments are indicated that some code should be added by you, or by your successor
developer and this can be used like a reminder.
To configure the levels of compilation, you can go to Tools > Options. In the form, click on
Development index and click on Compiler button. You can select the required options from there. The
below image shows how to navigate and configure the compilation levels from Development
environment:
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 444
Note: Level 4 will check for best practice warnings also. You may select others to avoid these and Level 1
will check only for critical errors.
You can also configure the best practices that should be verified by the compiler. Developer can
configure the best practices that should be verified by compiler at the time of compilation. This will give
flexibility of selecting what should be checked and what not. You can do this by clicking on the Best
practices button in the above screen which will show the dialog as shown in the below image. Check or
uncheck the Best practice checks you need to retain as per your requirement.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 445
Note: You dont get any problem with best practice errors even if you dont resolve them but it is
recommended to clear these for better understanding and working of the program.
With enough information about the best practices and the conventions, its time to move a step
ahead and understand the tips and tricks to work with Microsoft Dynamics AX. These tips are not rules
and are not defined but can be used by a fresh or experienced developer while working in Microsoft
Dynamics AX.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 446
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 447
Tips and Tricks while working in AX Environment
While developing various objects or coding the methods in X++, you may encounter few
scenarios which may include the following scenarios:
Using scripts:
o When you need to code for scripts, there are few templates available in AX code
editor. You can use them instead of coding line by line.
XML documentation is a better practice to include for new methods you code which will
provide more information that can be used to identify the use of method and the
parameters that should be passed to the method looking at the comments itself. To add
these, you may not need to write each line by line. Just type /// and press enter. You will
see the template inserted into your code. Update that accordingly.
When you work with postings in AX, it is observed that few developers try to understand
the internal code and simulate the same in their programming requirements. You can
use the existing framework to do so instead of coding from scratch and simulate the
same. Trust me, there will be lot of updates done internally and if you try it will become
your framework where you are wasting your time in developing some existing stuff.
While working with AX, always try to use the existing queries, classes, forms and other
objects as much possible. You should create a new object only if you cannot proceed
with your work until and unless you create that object.
Use templates where ever available and applicable for quick development. Trust me, a
good form will not take more than 2 hours which may have around 4-5 data sources
which will only display fields and values. Effort of coding on depends on the logic and
requirement size.
Always teach end users how they can personalize and configuring batches etc. which
will reduce the effort of consultant few times.
Use shortcuts as much you can and better to consider fixing all the best practice errors,
warnings and ToDos.
You can consider using the tools and find how the existing code updates something you
like to do which will make your development quick and understand better.
Use Add-ins to find few menu objects linked with buttons etc. instead of searching for
them in AOT.
Always compare the code before you import the code from XPO to avoid mishaps and
the better way is always use model driven programming.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 448
Please note that any number of tricks or tips will be one less than required. With enough
number of tips or tricks, hope that this book covered good stuff and request your feedback at each level
for betterment of next edition and other books that will be released from this press.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 449
Links
www.axaptaschool.com: This is author managed site and you can find lot of information and posts in
this site.
AX official site: This site is official Microsoft Dynamics AX site which will have some interesting
information and resources from Microsoft.
Microsoft Dynamics AX Developer Center: You'll find the tools and resources available for developers
working with the Microsoft Dynamics AX 2012 in this site.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 450
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 451
Upcoming Releases
DAX Softronics is always committed to give best for its readers and
audience. DAX Softronics always plans to give the best releases and planned to
give few new books for its readers in very near future. You can check
www.daxsoftronics.com and www.axaptaschool.com for updates of the titles that
are planned soon.
We request your valuable feedback ever to get the best in the next versions
and releases.
DAX Softronics is committed to encourage young and new authors and
contribute itself to grow the talent. We request you to connect DAX Softronics
web site and submit your application and title information. We are here always to
promote your creative work.
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 452
DAX Hand Book
T A S K K u m a r w w w . A x a p t a S c h o o l . c o m Page 453
Happy DAXING
Thank you

You might also like