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.
T A S K Kumar
www.AxaptaSchool.com
Page 5
DAX Hand Book
T A S K Kumar
www.AxaptaSchool.com
Page 6
DAX Hand Book
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.
T A S K Kumar
www.AxaptaSchool.com
Page 7
DAX Hand Book
T A S K Kumar
www.AxaptaSchool.com
Page 8
DAX Hand Book
Contents at a Glance Introduction Unit I 1. Architectural Overview 2. ERP and AX 3. MorphX Environment Unit II 4. 5. 6. 7. 8. 9.
X++ Introduction Object Oriented Programming in X++ Data Dictionary Macros Queries Resources
10. 11. 12. 13. 14. 15.
Forms Reports Menus and Menu Items Files Frameworks Miscellaneous
Unit III
Appendixes 16. 17. 18. 19. 20. 21. 22. 23.
New improvements and CIL in AX 2012 Installation and configuration Using Debugger in AX Label Wizard and Label Editor Basic Administration Interesting tools in AX Development environment Best practices and their requirement Tips while working in AX development environment.
Links
T A S K Kumar
www.AxaptaSchool.com
Page 9
DAX Hand Book
T A S K Kumar
www.AxaptaSchool.com
Page 10
DAX Hand Book
Table of Contents Introduction Unit I 1. Architectural Overview a. 3-Tier Architecture b. Layered Architecture and Model Store c. Application Development and Run Time Environments d. Application Framework 2. ERP and AX a. What is ERP b. AX and ERP c. Modules 3. MorphX Environment a. Application Object Tree
21 21 22 25 26 27 27 29 30 33 33
4. X++ Introduction a. Programming in Microsoft Dynamics AX b. Jobs c. Variables and Types d. Operators e. Control Flow Statements f. Built-in functions g. Basic Input and Output 5. Object Oriented Programming in X++ a. Classes i. Creating Classes ii. Variables/Methods/Constructors/Destructors/Access Modifiers iii. Encapsulation iv. Inheritance v. Interfaces and Miscellaneous Concepts vi. Polymorphism vii. Eventing viii. Creating and using Attributes ix. Importance of Classes in DAX b. Types of Classes i. System Classes ii. Application Classes c. Exception Handling d. Other Fundamental Classes [Collections]
DAX Hand Book 6. Data Dictionary a. Extended Data Types i. What are EDTs ii. Power of EDTs in DAX iii. EDT Relations iv. Array Elements in EDT b. Base Enums i. What are Base Enums ii. How to create Base Enums c. Tables i. Basic Persistent Store ii. System Tables and Application Tables iii. Creating Tables/Fields/Various Data Types and Usage iv. Using EDTs and Base Enums v. How the tables in DAX store data vi. Basic Table Properties and Field Properties vii. Field Groups and Requirement viii. Table Indexing/Primary Index/Pros and Cons of Indexing ix. Surrogate Keys x. Table Relations and Types of Table Relations xi. Difference Between EDT Relations and Table Relations xii. Delete Actions/How to play with Cascade + Restricted xiii. Methods as Events in Table/Alternative to PL/SQL Triggers xiv. Transaction Tracking System xv. New Features and Differences in DAX 6 compared to DAX 5 a) Table Inheritance d. Maps i. What are Maps ii. Creating and Using Maps iii. Where and Where not to use Maps e. Views i. What are Views ii. Requirement of Views iii. Creating Simple and Complex Views iv. Methods in Views f. License Codes g. Configuration Keys i. What are Configuration Keys ii. Usage and Applying Configuration Keys in Practical h. Security Keys i. Requirement of Security Keys
7. Macros a. Macro Commands b. Constants c. Create Macros d. Passing Values 8. Queries a. What are Queries and Requirement of Queries b. Inline Queries i. What are Inline Queries ii. Using loops 1. Why Inline Queries in spite of Other Queries available c. AOT Queries i. Creating AOT Queries ii. Using AOT Queries in X++ iii. Child Data Sources/Ranges/Sorting and Ranges iv. Data Source Relations v. Data Sources and the properties of Data Sources vi. Composite Queries vii. Why AOT Queries and What is the use of AOT Queries viii. Methods in Queries d. X++ Queries i. Creating and Using X++ Queries ii. Data Sources/Ranges/Child Data Sources/Relations e. When and Where to use each type of Query f. Optimize CRUD Operations 9. Resources a. What are Resources b. Adding and Using Resources
ii. Applying Security Keys in DAX Table Collections i. What are Table Collections ii. Why Table Collections iii. Using Virtual Companies and Table Collections
Unit IV
T A S K Kumar
Basics of Forms Existing Forms Templates available in AX 2012 Creating New Forms Using Data Sources
www.AxaptaSchool.com
Page 13
DAX Hand Book
11.
12.
13.
14.
15.
f. Using resources in a form g. Joining Data Sources h. Methods in Forms/Methods in Data Sources/Form Data Source Field Methods i. Method Calling Sequence j. Placement of code k. Adding filters to forms l. Bound and Un bound Controls/Methods on Controls m. The power of Display and Edit Modifiers n. Using ManagedHost, Splitters etc. o. Calling a form from code p. Identify existing forms and personalization Reports a. Basic Report Wizard [MorphX Reports] b. Important Methods in Reports c. The Power of Programmable Sections d. Auto Design and Generated Design e. SQL Server Reporting Services i. Using Visual Studio to Develop SQL Server Reports ii. Auto design and precision design iii. Adding the report to AX iv. Deploying the reports from AX v. Opening and editing existing reports Menus and Menu Items a. Different Types of Menu Items b. Menus Files a. Reading and Writing Text Files b. Creating CSV Files c. Reading and Writing XML Files d. Exporting the Data to Excel Documents Frameworks a. Runbase Framework b. SysOperationFramework c. Number Sequence Framework Miscellaneous a. Using .NET Classes in AX
Appendixes 16. New improvements and CIL in AX 2012 17. Installation and configuration of AX 2012 18. Using Debugger in AX
T A S K Kumar
www.AxaptaSchool.com
405 407 409
Page 14
DAX Hand Book
19. 20. 21. 22. 23.
a. Debug Standard AX X++ Code b. Configuring Debugger in AX c. Using configuration utility Label Wizard and Label Editor Basic Administration Interesting tools in AX development environment Best Practices and their Requirement Tips while working in AX Environment
409 410 411 417 427 437 443 447
Links - 449
T A S K Kumar
www.AxaptaSchool.com
Page 15
DAX Hand Book
T A S K Kumar
www.AxaptaSchool.com
Page 16
DAX Hand Book
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. It’s 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, I’d like to thank you for choosing the book as your choice of learning Microsoft Dynamics AX. I’m sure, you don’t 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 don’t 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
T A S K Kumar
www.AxaptaSchool.com
Page 17
DAX Hand Book 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 I’m sure, you’ll 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. I’d really love to hear your feed back at mailto:[email protected]. Thanks, T A S K Kumar. Axapta School.
T A S K Kumar
www.AxaptaSchool.com
Page 18
DAX Hand Book
Unit I 1. Architectural Overview 2. ERP and DAX 3. MorphX Environment
T A S K Kumar
www.AxaptaSchool.com
Page 19
DAX Hand Book
T A S K Kumar
www.AxaptaSchool.com
Page 20
DAX Hand Book
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.
T A S K Kumar
www.AxaptaSchool.com
Page 21
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 22
DAX Hand Book 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.
T A S K Kumar
www.AxaptaSchool.com
Page 23
DAX Hand Book
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:
T A S K Kumar
www.AxaptaSchool.com
Page 24
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 25
DAX Hand Book 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.
T A S K Kumar
www.AxaptaSchool.com
Page 26
DAX Hand Book
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
T A S K Kumar
www.AxaptaSchool.com
Page 27
DAX Hand Book Data Services Various "self–service" 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 real–time 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.
T A S K Kumar
www.AxaptaSchool.com
Page 28
DAX Hand Book
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 purposebuilt 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
T A S K Kumar
www.AxaptaSchool.com
Page 29
DAX Hand Book
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.
T A S K Kumar
www.AxaptaSchool.com
Page 30
DAX Hand Book
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
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 SALES AND MARKETING • Sales force and marketing automation • Lead and opportunity management • Sales management • Microsoft Dynamics CRM connector
PROJECT MANAGEMENT AND ACCOUNTING • Project accounting and invoicing • Project cost control • Work breakdown structures • Integration with Microsoft Project
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 PROCUREMENT AND SOURCING • Direct and indirect procurement • Purchase requisitions • Supplier relationship management • Vendor self-service portal 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 o
Microsoft Visual Studio and Microsoft .NET Windows Communication Foundation and Windows Workflow Foundation
Business Productivity Solutions: o o
T A S K Kumar
Microsoft Outlook, Excel, Word Microsoft Lync and SharePoint
www.AxaptaSchool.com
Page 31
DAX Hand Book
Application Platform: o o
T A S K Kumar
Microsoft SQL Server Microsoft BizTalk
www.AxaptaSchool.com
Page 32
DAX Hand Book
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.
T A S K Kumar
www.AxaptaSchool.com
Page 33
DAX Hand Book
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 don’t 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]
T A S K Kumar
www.AxaptaSchool.com
Page 34
Bread Crumb Bar
Content Pane
DAX Hand Book
File Menu and Shortcuts on right
Status Bar Navigation Pane
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 don’t need. The above image is only user workspace and developer workspace looks as shown in coming image:
T A S K Kumar
www.AxaptaSchool.com
Page 35
DAX Hand Book
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, it’s time to get into the basic development stuff. In this text, I’m starting from X++ programming rather than the regular approach to make developers feel
T A S K Kumar
www.AxaptaSchool.com
Page 36
DAX Hand Book 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. I’d like to receive the feedback on the approach for better organizing the structure of the book in further editions.
T A S K Kumar
www.AxaptaSchool.com
Page 37
DAX Hand Book
T A S K Kumar
www.AxaptaSchool.com
Page 38
DAX Hand Book
Unit II 1. 2. 3. 4. 5. 6.
T A S K Kumar
X++ Introduction Object Oriented Programming in X++ Data Dictionary Macros Queries Resources
www.AxaptaSchool.com
Page 39
DAX Hand Book
T A S K Kumar
www.AxaptaSchool.com
Page 40
DAX Hand Book
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++ doesn’t 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.
T A S K Kumar
www.AxaptaSchool.com
Page 41
DAX Hand Book 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:
T A S K Kumar
www.AxaptaSchool.com
Page 42
DAX Hand Book 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 Blue Green Dark Red Bright Red Purple Black
Code type Reserved words Comments Strings Numbers Labels 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, let’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 43
DAX Hand Book
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.
T A S K Kumar
www.AxaptaSchool.com
Page 44
DAX Hand Book 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: /// ///Comment using XML tags to distinguish sections. ///
T A S K Kumar
www.AxaptaSchool.com
Page 45
DAX Hand Book 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, it’s 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, let’s 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
String
A string is a set of characters. X++ Str 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. An integer, also named a natural Int digit, is a number without decimal point. Null value is represented by 0. Real values, also called decimals, are Real digits with a decimal point. Null value
Integer
Real
T A S K Kumar
Keyword
www.AxaptaSchool.com
Example Declaration CustomerName
1090
3.14
Page 46
DAX Hand Book
Date UTCDateTime
Enum
Boolean
Time
GUID
Int64
is represented by 0.0. The date type contains day, month, and year. This type contains year, month, day, hour, minute and second. Null value is represented by 1900-01-01. 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. Booleans can only contain the values false and true. The values false and true are predefined in X++ and recognized by the compiler. This type contains hours, minutes, and seconds. To declare a time, use the system type timeOfDay. Null value is represented by 00:00:00. Global Unique Identifier (GUID) is a reference number which is unique in any context. A large integer, represented by 64 bits.
Date
12/22/2012
utcDateTime
12/22/2012 04:00:59 am
Must be declared Gender as a BaseEnum first
Boolean
TRUE
timeOfDay
04:00:59
Guid
{5D2503A0-4S8055D3-0P9C2908H82C3804} 11234567890
int64
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: ; Following are few examples for the above shown types: int
T A S K Kumar
integerVariable;//This declares a variable of type integer
www.AxaptaSchool.com
Page 47
DAX Hand Book real str str 30 date boolean
realVariable; //This declares a variable which can store real values unboundStringVariable;//This variable stores string with maximum length boundStringVariable; //This variable stores string with maximum of 30 characters dateVariable; //This variable stores date booleanVariable; //This variable can store a Boolean value i.e. True or False.
Now, let’s 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, let’s 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 Array Container
Description An array is a list of items with the same data type and the same name; only the index differs. 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.
T A S K Kumar
www.AxaptaSchool.com
Page 48
DAX Hand Book 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 conPeek conDel conNull conFind conIns conPoke conLen
Description Returns the value being held in a specific position in the container. Removes a value from a specific position in the container. Returns an empty container. Finds the position in the container that a certain value is being held (if found). Inserts a value into a specific position in the container. Replaces the value being held in a specific position in the container, with a new value. 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), it’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 49
DAX Hand Book 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, it’s 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. Let’s check one more program as follows: boolean
boolValue = false;
if(boolValue) {
T A S K Kumar
www.AxaptaSchool.com
Page 50
DAX Hand Book }
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. Let’s 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 didn’t 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. }
T A S K Kumar
www.AxaptaSchool.com
Page 51
DAX Hand Book Now, it’s 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. Let’s 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; }
T A S K Kumar
www.AxaptaSchool.com
Page 52
DAX Hand Book else { }
}
discount = 5;
The only thing I’d 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, let’s 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) {
T A S K Kumar
www.AxaptaSchool.com
Page 53
DAX Hand Book 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; }
T A S K Kumar
www.AxaptaSchool.com
Page 54
DAX Hand Book 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.
T A S K Kumar
www.AxaptaSchool.com
Page 55
DAX Hand Book Let’s 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, I’m 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.:
T A S K Kumar
www.AxaptaSchool.com
Page 56
DAX Hand Book
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);
T A S K Kumar
www.AxaptaSchool.com
Page 57
DAX Hand Book
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 doesn’t expect. Now that we are clear with the conditional statements, it’s 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:
Syntax
Working
Initialization
T A S K Kumar
While loop while(condition) { //Statements; }
Do while loop do { //Statements; }while(condition);
For loop for(initialization; condition; increment/decrement) { //Statements; } Will not enter into loop if Will enter into the loop Will not enter into loop if the condition fails for the even if the condition fails the condition fails for the first time. for the first time. first time. 1. Initialize 2. Check condition 3. Execute statements 4. Increment/decrement 5. Go to step 2 Initialization of the looping variable,
1. Initialize 2. Execute statements 3. Increment/decrement 4. Check condition 5. Go to step 2 Initialization of the looping variable,
www.AxaptaSchool.com
1. Initialize 2. Check condition 3. Execute statements 4. Increment/decrement 5. Go to step 2 Initialization of the looping variable,
Page 58
DAX Hand Book increment/decrement done separately. Execution
Example 1
is increment/decrement done separately.
is increment/decrement is done in for as shown in syntax. Will not execute even a Will execute once even if Will not execute even a single time if condition the condition fails. single time if condition fails. fails. int counter = 1; int counter = 1; int counter; while(i<=5) do for(i=1;i<=5;i++) { { { info(“This is example of info(“This is example of info(“This is example of while”); do-while”); for”); i = i+1; i = i+1; } } } while(i<=5);
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;
int counter = 1;
int counter;
while(i<=0) { info(“This is example of while”); i = i+1; } Condition is verified first and continues with execution.
do for(i=1;i<=0;i++) { { info(“This is example of info(“This is example of do-while”); for”); i = i+1; } } while(i<=0); This is example of do-while Execute first for once and Condition is verified first and later check for condition to continues with execution. execute loop further.
Output Difference
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 dowhile 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. Let’s see a small program which is taught to me
T A S K Kumar
www.AxaptaSchool.com
Page 59
DAX Hand Book 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, let’s 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++.
T A S K Kumar
www.AxaptaSchool.com
Page 60
DAX Hand Book
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 don’t 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, let’s 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 //Program to display for one number
Using for //Program to display for one number
int number = 10, counter, result;
int number = 10, counter, result;
counter = 1; while(counter <= 10) { result = number * counter; } In the above program, I’m trying to calculate multiplication table of only one number. So, I’ve 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. int number = 1, counter, result;
for(counter = 1; counter <= 10; counter++) { result = number * counter; }
T A S K Kumar
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, counter, result;
www.AxaptaSchool.com
Page 61
DAX Hand Book
while(number <= 10) { counter = 1; while(counter <= 10) { 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.
for (number = 1; number <= 10; number++) { for (counter = 1; counter <= 10; counter++) { result = number * counter; } } 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, let’s 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. Let’s 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. Let’s check the both examples as follows: Scenario 1
Scenario 2
int number=23, c; boolean p = true;
int number, c; boolean p = true;
for ( c = 2 ; c <= number/2 ; c++ ) { if ( number %c == 0 )
p = false; break; } } if ( p ) { info ("Given number is a prime number."); } else { info ("Given number is not prime 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.
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, 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.
T A S K Kumar
www.AxaptaSchool.com
Page 63
DAX Hand Book 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, I’m 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. Let’s 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().
T A S K Kumar
www.AxaptaSchool.com
Page 64
DAX Hand Book Let’s 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 subStr str text position number
Description Name of the function. Return type of the function. The string that is passed to function. Indicates the position where the sub string starts. 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, it’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 65
DAX Hand Book 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 Information Warning
Error
Use This is used for displaying general information to the user. 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. 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”);
T A S K Kumar
www.AxaptaSchool.com
Page 66
DAX Hand Book 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”);
T A S K Kumar
www.AxaptaSchool.com
Page 67
DAX Hand Book 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");
T A S K Kumar
www.AxaptaSchool.com
Page 68
DAX Hand Book 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. Let’s 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, let’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 69
DAX Hand Book understanding, let’s 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.”); } } Let’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 70
DAX Hand Book 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.
T A S K Kumar
www.AxaptaSchool.com
Page 71
DAX Hand Book
T A S K Kumar
www.AxaptaSchool.com
Page 72
DAX Hand Book
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 don’t 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, let’s 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. Let’s consider the other chance, declare variables global. Here, the variables are accessible not only to faculty’s methods but also student’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 73
DAX Hand Book 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.
T A S K Kumar
www.AxaptaSchool.com
Page 74
DAX Hand Book 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, let’s 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:
T A S K Kumar
www.AxaptaSchool.com
Page 75
DAX Hand Book
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. You’ll 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. You’ll 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, let’s 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:
T A S K Kumar
www.AxaptaSchool.com
Page 76
DAX Hand Book
As you can see in the above figure, the class name is changed to Calculator. While writing this, I’ve 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, let’s 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, let’s understand and modify the method declaration as per our requirement. The declaration has the following parts:
T A S K Kumar
www.AxaptaSchool.com
Page 77
DAX Hand Book method1():name of the method. void, which says that method don’t 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. Let’s 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() {
T A S K Kumar
www.AxaptaSchool.com
Page 78
DAX Hand Book 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:
T A S K Kumar
www.AxaptaSchool.com
Page 79
DAX Hand Book
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:
T A S K Kumar
www.AxaptaSchool.com
Page 80
DAX Hand Book Let’s 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: calculatorObject1 number1 number2 result
calculatorObject2 number1 number2 result parmNumber1() parmNumber2() add2numbers() ….
T A S K Kumar
www.AxaptaSchool.com
Page 81
DAX Hand Book 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, let’s 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 Private Public Protected
Static
Usage These members are accessible only through other members of same class. These members are accessible from anywhere in AX, provided, we have to create an instance and call these members using that object. 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. 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++. It’s 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;
T A S K Kumar
www.AxaptaSchool.com
Page 82
DAX Hand Book } 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; }
T A S K Kumar
www.AxaptaSchool.com
Page 83
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 84
DAX Hand Book 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.
Let’s 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, it’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 85
DAX Hand Book 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 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; }
T A S K Kumar
Option 2: Extend ordinary calculator 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;
www.AxaptaSchool.com
Page 86
DAX Hand Book 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.
T A S K Kumar
www.AxaptaSchool.com
Page 87
DAX Hand Book The following figure explain about how this is done: 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 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()
public class ScientificCalculator public int exponent(int _value, int _exp) public int factorial(int _number) public int square(int _number)
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 Calculator cob = new Calculator(); ScientificCalculator scob = new ScientificCalculator(); cob
Case 2 ScientificCalculator scob = new ScientificCalculator();
scob
scob
cob
In this case, every object is individual which will provide its own services.
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.
T A S K Kumar
www.AxaptaSchool.com
Page 88
DAX Hand Book 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 Private Public
T A S K Kumar
Usage These members are accessible only through other members of same class. Even child class methods cannot access these private methods. These members are accessible from anywhere in AX, provided, we have to create an
www.AxaptaSchool.com
Page 89
DAX Hand Book
Protected
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. 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.
Let’s 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. }
T A S K Kumar
www.AxaptaSchool.com
Page 90
DAX Hand Book
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:
T A S K Kumar
www.AxaptaSchool.com
Page 91
DAX Hand Book Single/Simple Inheritance
A B
Multilevel Inheritance
A B C Hierarchical Inheritance
A
B
D
C
Multiple Inheritance
A
B
C Hybrid Inheritance
A
B
C
D
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
T A S K Kumar
www.AxaptaSchool.com
Page 92
DAX Hand Book 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; }
T A S K Kumar
www.AxaptaSchool.com
Page 93
DAX Hand Book } 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:
T A S K Kumar
www.AxaptaSchool.com
Page 94
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; }
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() {
DAX Hand Book }
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, let’s try to understand what abstract methods are. Let us consider a scenario where we don’t 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 don’t 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.
T A S K Kumar
www.AxaptaSchool.com
Page 96
DAX Hand Book 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. Let’s see this using an example: 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"); }
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
T A S K Kumar
www.AxaptaSchool.com
Page 97
DAX Hand Book 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 don’t 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++ doesn’t 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
T A S K Kumar
www.AxaptaSchool.com
Page 98
DAX Hand Book 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");
T A S K Kumar
www.AxaptaSchool.com
Page 99
DAX Hand Book } 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 don’t know implementation for Shape. This method has a meaningful implementation in Rectangle and Square and that’s 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() {
T A S K Kumar
www.AxaptaSchool.com
Page 100
DAX Hand Book }
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;
T A S K Kumar
www.AxaptaSchool.com
Page 101
DAX Hand Book } You may ask me, “Why don’t 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 don’t have any other methods, I implemented the method though it is not mandatory to implement that. Now, let us suppose, we don’t 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 don’t 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()
T A S K Kumar
www.AxaptaSchool.com
Page 102
DAX Hand Book { 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, let’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 103
DAX Hand Book
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. Let’s 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();
T A S K Kumar
www.AxaptaSchool.com
Page 104
DAX Hand Book 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 PolyMethod()
PolyChild PolyMethod() ChildMethod()
PolyOtherChild 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 doesn’t know about
T A S K Kumar
www.AxaptaSchool.com
Page 105
DAX Hand Book 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. Let’s 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 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.
T A S K Kumar
www.AxaptaSchool.com
Page 106
DAX Hand Book I’m writing a couple of programs and leaving understanding to you. I request you to go through the programs multiple times if you don’t understand or mail me to the given mail id for further explanation: Sample 1 interface Intf { } public void implementMethod1() { }
Sample 2 abstract class AbstractClass { } abstract public void abstractMethodOne() { } abstract public void abstractMethodTwo() class Poly1 implements Intf { { } } public void ConcreteOne() public void implementMethod1() { { info("ConcreteOne() of AbstractClass"); info("Implementation of implementMethod1 in this.abstractMethodOne(); Poly1"); } } class ConcreteClass extends AbstractClass class Poly2 implements Intf { { } } public void abstractMethodOne() public void implementMethod1() { { info("abstractMethodOne() of ConcreteClass"); info("Implementation of implementMethod1 in } Poly2"); public void abstractMethodTwo() } { info("abstractMethodTwo() of ConcreteClass"); class Poly3 implements Intf } { } public void implementMethod1() static void PolyDemo(Args _args) { { info("Implementation of implementMethod1 in AbstractClass abstractClass = new Poly3"); ConcreteClass(); } abstractClass.ConcreteOne(); static void PolyDemo(Args _args) } {
T A S K Kumar
www.AxaptaSchool.com
Page 107
DAX Hand Book Intf intfReference = new Poly1(); intfReference.implementMethod1(); intfReference = new Poly2(); intfReference.implementMethod1(); intfReference = new Poly3(); intfReference.implementMethod1(); } Understand the calls and sequence in this An abstract method is called in class AbstractClass program. 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. Let’s see a small example on how to create events and add the events to methods step wise: (i) (ii)
(iii)
(iv)
T A S K Kumar
Identify the method for which you like to write event. 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. 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. I’ve selected Post as in my case, I like to execute the event after the method execution is done. 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:
www.AxaptaSchool.com
Page 108
DAX Hand Book
(v)
(vi) (vii) (viii)
public static void eventHandlerMethod(XppPrePostArgs _args) { info("This is eventHandler for event demo"); } 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. 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. Now, set the property Method to the method name just created, here, it is, “eventHandlerMethod”. 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();
T A S K Kumar
www.AxaptaSchool.com
Page 109
DAX Hand Book }
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 { }
T A S K Kumar
www.AxaptaSchool.com
Page 110
DAX Hand Book [DemoAttribute(“AttributeValue”)] public void AttributeUsageInMethod() { } With a hope that we have covered good stuff with OOPs, I’ll 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.
T A S K Kumar
www.AxaptaSchool.com
Page 111
DAX Hand Book 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 don’t 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. Let’s see the statements in application with a sample as follows: public void div2numbers(int _number1, int _number2) {
T A S K Kumar
www.AxaptaSchool.com
Page 112
DAX Hand Book 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. It’s 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 don’t handle exceptions, the runtime environment will throw exception and will stop execution. If we handle the exceptions, after exception
T A S K Kumar
www.AxaptaSchool.com
Page 113
DAX Hand Book 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 Deadlock Error Internal Break CLRError CodeAccessSecurity UpdateConflict
Description Thrown when there is a database deadlock because several transactions are waiting for one another. Thrown when a fatal problem has occurred and the transaction has stopped. Thrown when Microsoft Dynamics AX encounters internal development problems at runtime. Thrown when the user presses Break or Ctrl+C during runtime. Thrown when a problem occurs during the use of the Common Language Runtime (CLR) functionality. Thrown when a problem occurs during the use of CodeAccessPermission.demand(). 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++:
T A S K Kumar
www.AxaptaSchool.com
Page 114
DAX Hand Book Collection class Array
List
Map
Set
Struct
Description 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 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 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 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.
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);
T A S K Kumar
www.AxaptaSchool.com
Page 115
DAX Hand Book 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)
T A S K Kumar
www.AxaptaSchool.com
Page 116
DAX Hand Book { 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.
T A S K Kumar
www.AxaptaSchool.com
Page 117
DAX Hand Book 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)));
T A S K Kumar
www.AxaptaSchool.com
Page 118
DAX Hand Book } 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()));
T A S K Kumar
www.AxaptaSchool.com
Page 119
DAX Hand Book } } 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()); }
T A S K Kumar
www.AxaptaSchool.com
Page 120
DAX Hand Book 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++, it’s time to move to more interesting stuff coming in next chapter.
T A S K Kumar
www.AxaptaSchool.com
Page 121
DAX Hand Book
T A S K Kumar
www.AxaptaSchool.com
Page 122
DAX Hand Book
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. Let’s 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. I’m sure, I’ll 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. EDT’s are the types created from basic or another EDT. EDTs extend primitive types or other EDTs
T A S K Kumar
www.AxaptaSchool.com
Page 123
DAX Hand Book 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 don’t need any extra EDTs to define. Let’s 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, it’s time to create an EDT for TruckId which is not available, and as per the requirement we don’t 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, let’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 124
DAX Hand Book 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, let’s 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, let’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 125
DAX Hand Book 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, you‘ll 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.
T A S K Kumar
www.AxaptaSchool.com
Page 126
DAX Hand Book
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.
T A S K Kumar
www.AxaptaSchool.com
Page 127
DAX Hand Book
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 nonchangeable. 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.
T A S K Kumar
www.AxaptaSchool.com
Page 128
DAX Hand Book With enough information, let’s 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. It’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 129
DAX Hand Book
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 let’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 130
DAX Hand Book 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 field’s 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 it’s 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:
T A S K Kumar
www.AxaptaSchool.com
Page 131
DAX Hand Book
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 field’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 132
DAX Hand Book 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. It’s the great advantage in AX, which we don’t 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 logo’s 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 don’t 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.
T A S K Kumar
www.AxaptaSchool.com
Page 133
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 134
DAX Hand Book 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 don’t 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 table’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 135
DAX Hand Book 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 don’t 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, it’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 136
DAX Hand Book 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. Let’s 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, it’s 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:
T A S K Kumar
www.AxaptaSchool.com
Page 137
DAX Hand Book 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, it’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 138
DAX Hand Book 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, let’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 139
DAX Hand Book 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:
T A S K Kumar
www.AxaptaSchool.com
Page 140
DAX Hand Book
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. Let’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 141
DAX Hand Book 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, let’s 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 didn’t 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
T A S K Kumar
www.AxaptaSchool.com
Page 142
DAX Hand Book 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, it’s 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, let’s consider the scenario where the data in TruckTable should be deleted when we delete record in Zones table. Let’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 143
DAX Hand Book 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 aren’t committed until the entire transaction is complete. The following table describes various DeleteActions available in AX 2012: Delete Action None
Cascade
Restricted
Cascade + Restricted
Comments 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. 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. 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. 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. It’s 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:
T A S K Kumar
www.AxaptaSchool.com
Page 144
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 145
DAX Hand Book 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 Selecting records
Sample Program 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
T A S K Kumar
www.AxaptaSchool.com
Page 146
DAX Hand Book 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])); } }
Insert records
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. 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;
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. 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";
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. 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
T A S K Kumar
www.AxaptaSchool.com
Page 149
DAX Hand Book 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(). Let’s understand a sample how to validate field value using this
T A S K Kumar
www.AxaptaSchool.com
Page 150
DAX Hand Book 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, let’s 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 don’t
T A S K Kumar
www.AxaptaSchool.com
Page 151
DAX Hand Book 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) {
T A S K Kumar
www.AxaptaSchool.com
Page 152
DAX Hand Book 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);
T A S K Kumar
www.AxaptaSchool.com
Page 153
DAX Hand Book 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. Let’s 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 day’s date. This may be modified by the user at later point when
T A S K Kumar
www.AxaptaSchool.com
Page 154
DAX Hand Book 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; }
T A S K Kumar
www.AxaptaSchool.com
Page 155
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 156
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 157
DAX Hand Book 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. Let’s 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; }
T A S K Kumar
www.AxaptaSchool.com
Page 158
DAX Hand Book 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 public boolean validateWrite() {
T A S K Kumar
Before super() call public boolean validateWrite() {
www.AxaptaSchool.com
Page 159
DAX Hand Book boolean ret;
boolean ret;
ret = super(); if(!this.Description) { ret = ret && checkFailed("Description should not be empty."); } 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.
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. 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.
T A S K Kumar
www.AxaptaSchool.com
Page 160
DAX Hand Book 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, it’s 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 didn’t 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.
T A S K Kumar
www.AxaptaSchool.com
Page 161
DAX Hand Book 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 don’t 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:
T A S K Kumar
www.AxaptaSchool.com
Page 162
DAX Hand Book 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, it’s 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:
T A S K Kumar
All constraint definitions (primary key, unique, and referential constraints)
www.AxaptaSchool.com
Page 163
DAX Hand Book 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
AccountNum
Name
CardNumber
AmountAvailable
AmountAvailable
DateOfCreation
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
T A S K Kumar
www.AxaptaSchool.com
Page 164
DAX Hand Book inheritance feature, follow the steps below. This example will create table from scratch for clear explanation:
T A S K Kumar
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. Don’t 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. That’s 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:
www.AxaptaSchool.com
Page 165
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 166
DAX Hand Book 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
T A S K Kumar
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 don’t plan table inheritance based only on this clue. Finally, child table is not meant for performance improvement of physical database.
www.AxaptaSchool.com
Page 167
DAX Hand Book 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, it’s 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, it’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 168
DAX Hand Book 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.
T A S K Kumar
www.AxaptaSchool.com
Page 169
DAX Hand Book 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 don’t 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, it’s time see an example of creating and using a map. To do this exercise, we don’t 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 don’t 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 EDT’s, 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.
T A S K Kumar
www.AxaptaSchool.com
Page 170
DAX Hand Book 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 don’t 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, let’s 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, I’m 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)); } }
T A S K Kumar
www.AxaptaSchool.com
Page 171
DAX Hand Book 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; }
T A S K Kumar
www.AxaptaSchool.com
Page 172
DAX Hand Book } 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
T A S K Kumar
www.AxaptaSchool.com
Page 173
DAX Hand Book 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 table’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 174
DAX Hand Book
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 field’s 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 view’s 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. I’m 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",
T A S K Kumar
www.AxaptaSchool.com
Page 175
DAX Hand Book "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 field’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 176
DAX Hand Book 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:
T A S K Kumar
www.AxaptaSchool.com
Page 177
DAX Hand Book
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."; }
T A S K Kumar
www.AxaptaSchool.com
Page 178
DAX Hand Book
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, let’s understand what happens if we disable configuration key for a table or column: AX 2009 and 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 previous versions 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, let’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 179
DAX Hand Book
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.
T A S K Kumar
www.AxaptaSchool.com
Page 180
DAX Hand Book 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:
T A S K Kumar
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.
www.AxaptaSchool.com
Page 181
DAX Hand Book 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. Let’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 182
DAX Hand Book 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.
T A S K Kumar
www.AxaptaSchool.com
Page 183
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 184
DAX Hand Book 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, it’s 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:
T A S K Kumar
www.AxaptaSchool.com
Page 185
DAX Hand Book
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.
T A S K Kumar
www.AxaptaSchool.com
Page 186
DAX Hand Book
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 it’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 187
DAX Hand Book 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 }
T A S K Kumar
www.AxaptaSchool.com
Page 188
DAX Hand Book 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 doesn’t 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:
T A S K Kumar
www.AxaptaSchool.com
Page 189
DAX Hand Book 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)
T A S K Kumar
www.AxaptaSchool.com
Page 190
DAX Hand Book 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 don’t get any error, instead, it works same until the macro name is not overridden in local context using #define or #localmacro.
T A S K Kumar
www.AxaptaSchool.com
Page 191
DAX Hand Book 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 , you‘ll 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.
T A S K Kumar
www.AxaptaSchool.com
Page 192
DAX Hand Book 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, it’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 193
DAX Hand Book
T A S K Kumar
www.AxaptaSchool.com
Page 194
DAX Hand Book
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 column’s values and extract the information you need using the queries. Queries are the only way to extract table’s 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 where, parameters may be one or more of these:
T A S K Kumar
www.AxaptaSchool.com
Page 195
DAX Hand Book [ [ 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
static void InlineQueries(Args _args) { Selecting a specific record. You can observe that a TruckTable truckTable; where clause is used for selecting a particular record. Here, the record is filtered using a string //Select a specific record value and enum value. You can also filter using any select truckTable where truckTable.TruckId == type like integer, real etc. "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 static void SelectionDemo(Args _args) { Selecting all the records one by one with specific TruckTable truckTable; fields i.e. field list. //Select all records one by one from the table We use while select to select the records one by while select TruckId, Description from one. The field list is given to select only particular truckTable fields which will increase performance of query { when you don’t need all the fields. Note that the info(strFmt("%1, %2", truckTable.TruckId,
T A S K Kumar
www.AxaptaSchool.com
Page 196
DAX Hand Book fields which you are not selecting will return truckTable.Description)); empty values if you try to use them in your } program. } Using aggregate functions static void InlineQueries(Args _args) { Selecting using aggregate functions. You have the TruckTable truckTable; following aggregate functions: //Use aggregate function count() avg – Used to find average select count(TruckId) from truckTable; count – Used to find count info(strFmt("%1", truckTable.TruckId)); sum – Used to find sum maxof – Used to find maximum //Use aggregate function maxOf() minof – Used to find minimum select TruckId, maxOf(TruckDimensions) from truckTable; In the example you can observe usage of count() info(strFmt("%1", and maxOf(). Note that, no other fields are truckTable.TruckDimensions[4])); 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. Using order by clause static void InlineQueries(Args _args) { Order by clause is used to sort the records in TruckTable truckTable; ascending or descending order. You give a field name using which the records are sorted in order //Select all records in a table in ascending sorting and retrieved from database table. The example order shows sorting TruckTable records in ascending and while select TruckId, Description, TruckType from descending orders in the order of TruckId. 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)); }
T A S K Kumar
www.AxaptaSchool.com
Page 197
DAX Hand Book } Using group by clause static void InlineQueries(Args _args) { The example uses the group by clause with a field. TruckTable truckTable; Note that most probably grouping is used only on the fields which will be used multiple times in a //Select all records in a table with grouping options column like the account type or customer type etc. while select sum(TruckDimensions), TruckType Usually, an enum represents this. You can also use from truckTable group by truckTable.TruckType grouping on fields like customer id in transactions { table where you can find the sum of amount or the info(strFmt("%1, %2", count of transactions done by the customer. truckTable.TruckDimensions[4], truckTable.TruckType)); Group by is mostly used in combination with } aggregate functions. //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 static void InlineQueries(Args _args) { You can select a value from EDT array using the TruckTable truckTable; index of the array as shown in the example. //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 static void InlineQueries(Args _args) { The reverse keyword is used to fetch the records in TruckTable truckTable; reverse order as in the given example. This works more or less equivalent to sorting in descending //Selection of records in reverse order
T A S K Kumar
www.AxaptaSchool.com
Page 198
DAX Hand Book order.
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.
while select reverse truckTable order by TruckId { info(strFmt("%1, %2, %3", truckTable.TruckId, truckTable.Description, truckTable.TruckDimensions[3])); } } //Using firstFast static TruckTable find(TruckId _truckId, boolean _forUpdate = false) { TruckTable truckTable; ;
if (_truckId) firstOnly is used to select only the first available { record. Though a simple select will return only one select firstFast truckTable record, few places like finding the first related where truckTable.TruckId == _truckId; record or checking for existence of record in related table needs firstOnly keyword. Note that, if (_forUpdate) firstOnly may not be used in while select truckTable.selectForUpdate(_forUpdate); statement where you may need to select more } than one record. return truckTable; } In addition to firstOnly, you have few more statements, //Using firstOnly firstOnly10, which is same as firstOnly except that public static boolean exist(TruckId _truckId) this returns 10 rows instead of one. { firstOnly100, which is same as firstOnly except that boolean found; this returns 100 rows instead of one. ; firstOnly1000, which is same as firstOnly except that this returns 1000 rows instead of one. found = (select firstOnly RecId from truckTable where truckTable.TruckId == _truckId).RecId != 0; return found; }
T A S K Kumar
www.AxaptaSchool.com
Page 199
DAX Hand Book Using index and index hint
static void InlineQueries(Args _args) { Index keyword is used to instruct the database to TruckTable truckTable; sort the selected records as defined by the index whereas index hint gives the database a hint to //Selection of records using index use this index to sort the selected records as while select TruckId, Description, TruckDimensions defined by the index. The database may ignore the from TruckTable index TruckIdx hint. { info(strFmt("%1, %2, %3", truckTable.TruckId, Please note that improper use of index hint will truckTable.Description, cause serious performance issues. Index hint truckTable.TruckDimensions[3])); should be applied only to the SQL statements that } do not have dynamic where clauses and/or order by clauses. //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 static void InlineQueries(Args _args) { This will not fetch any record into the table buffer TruckTable truckTable; and you see empty output in the infolog. You can use this to pass the table buffer to a method which //Selection of records using index will do actual fetch operation on database table. select nofetch TruckId, Description, TruckDimensions from TruckTable; Indicates that no records are to be fetched at present. This is typically used when the result of info(strFmt("%1, %2, %3", truckTable.TruckId, the select is passed on to another application truckTable.Description, object, for example, a query that performs the truckTable.TruckDimensions[3])); actual fetch. }
Using forUpdate
static void UpdateRecords(Args _args) { forUpdate is used to select the records exclusively TruckTable truckTable; for update. When you need to update a value or values of a selected record, you should select the //Select a record using forUpdate to update record
T A S K Kumar
www.AxaptaSchool.com
Page 200
DAX Hand Book record for update. It is mandatory to select the ttsBegin; record for update if you like to update the record select forUpdate truckTable where and If you don’t select for update, you will get truckTable.TruckId == "T0005"; errors. For more information, see transaction tracking system section on “updating of records truckTable.TruckType = TruckType::Large; and for update”. truckTable.update(); ttsCommit; } Using concurrency controls static void InlineQueries(Args _args) { You can also specify the concurrency models to TruckTable truckTable; use while selecting records from a table. Microsoft Dynamics AX supports the following concurrency //Select a record using optimistic concurrency control techniques: model select optimisticLock truckTable where optimisticLock: Optimistic Concurrency Control truckTable.TruckId == "T0005"; helps increase database performance. Optimistic info(strFmt("%1, %2, %3", truckTable.TruckId, Concurrency locks records from the time when the truckTable.Description, actual update is performed. truckTable.TruckDimensions[3])); 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.
//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
T A S K Kumar
www.AxaptaSchool.com
Page 201
DAX Hand Book 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++.
T A S K Kumar
www.AxaptaSchool.com
Page 202
DAX Hand Book 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, let’s 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:
T A S K Kumar
www.AxaptaSchool.com
Page 203
DAX Hand Book 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 don’t 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
T A S K Kumar
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:
www.AxaptaSchool.com
Page 204
DAX Hand Book 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 don’t 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
T A S K Kumar
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:
www.AxaptaSchool.com
Page 205
DAX Hand Book 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 doesn’t 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.
DAX Hand Book 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 don’t 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,
T A S K Kumar
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:
www.AxaptaSchool.com
Page 207
DAX Hand Book
both the tables are populated and for the records that don’t 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 don’t 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
T A S K Kumar
www.AxaptaSchool.com
Page 208
DAX Hand Book parent table and child that have related records in child table matching given criteria and also the parent table records which don’t 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.
T A S K Kumar
www.AxaptaSchool.com
Page 209
DAX Hand Book QueryRun Query QueryBuildDataSource (n) QueryBuildFieldList (1) QueryBuildRange (n) QueryBuildDynaLink (n) QueryBuildDataSource (n) QueryBuildFieldList (1) QueryBuildRange (n) QueryBuildLink (n) QueryBuildDataSource (n) The following table explains about each class in the framework in detail: QueryRun
Query
QueryBuildDataSource
QueryBuildFieldList
T A S K Kumar
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. 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. 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. 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
www.AxaptaSchool.com
Page 210
DAX Hand Book
QueryBuildRange
QueryBuildDynaLink
QueryBuildLink
QueryFilter
QueryBuildFieldList object. You can also specify aggregate functions on the fields. 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. 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. 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. 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, it’s 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, it’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 211
DAX Hand Book
the Dynamic property value to either Yes or No is mandatory and you will get an error if you don’t 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 {
T A S K Kumar
www.AxaptaSchool.com
Page 212
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 213
DAX Hand Book 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, let’s 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.
T A S K Kumar
www.AxaptaSchool.com
Page 214
DAX Hand Book 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
T A S K Kumar
www.AxaptaSchool.com
Page 215
DAX Hand Book QueryBuildRange reference and add the value to that range using the method value(). Don’t 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, it’s 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
T A S K Kumar
www.AxaptaSchool.com
Page 216
DAX Hand Book 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, let’s 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 don’t set this property, instead, we select the records using inline queries to update on the fly using forUpdate and TTS.
T A S K Kumar
www.AxaptaSchool.com
Page 217
DAX Hand Book 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 {
T A S K Kumar
www.AxaptaSchool.com
Page 218
DAX Hand Book 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 didn’t 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
T A S K Kumar
www.AxaptaSchool.com
Page 219
DAX Hand Book 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:
DAX Hand Book 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.
T A S K Kumar
www.AxaptaSchool.com
Page 221
DAX Hand Book 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