AX2012_ENUS_DEV_3
November 13, 2016 | Author: sergio_741 | Category: N/A
Short Description
ax 2012 dev III...
Description
COURSE: 80312
DEVELOPMENT III IN MICROSOFT DYNAMICS® AX 2012
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Last Revision: August 2011 This courseware is provided “as-is”. Information and views expressed in this courseware, including URL and other Internet Web site references, may change without notice. Unless otherwise noted, the examples depicted herein are provided for illustration only and are fictitious. No real association or connection is intended or should be inferred. This courseware does not provide you with any legal rights to any intellectual property in any Microsoft product. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this courseware may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means or for any purpose, without the express written permission of Microsoft Corporation. Copyright © 2011 Microsoft Corporation. All rights reserved. Microsoft®, Microsoft Dynamics®, Microsoft® PowerPoint®, Microsoft® SQL Server® data management software and Microsoft Dynamics® AX are trademarks of the Microsoft group of companies. All other trademarks are property of their respective owners. This course content is designed for Microsoft Dynamics® AX 2012.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Table of Contents Introduction
0-1
Welcome ............................................................................................................ 0-1 Microsoft Dynamics Courseware Contents ........................................................ 0-2 Documentation Conventions .............................................................................. 0-3 Student Objectives ............................................................................................. 0-4
Chapter 1: X++ Unit Test Framework
1-1
Objectives ........................................................................................................... 1-1 Introduction ......................................................................................................... 1-1 Creating Test Cases ........................................................................................... 1-2 Adding Methods to Test Cases .......................................................................... 1-3 Running Test Cases ........................................................................................... 1-5 Build Test Projects and Suites ........................................................................... 1-7 Lab 1.1 - Create a Test Case ........................................................................... 1-11 Summary .......................................................................................................... 1-13 Test Your Knowledge ....................................................................................... 1-14 Quick Interaction: Lessons Learned ................................................................. 1-16 Solutions ........................................................................................................... 1-17
Chapter 2: Working with Data
2-1
Objectives ........................................................................................................... 2-1 Introduction ......................................................................................................... 2-1 While Select ....................................................................................................... 2-2 Query .................................................................................................................. 2-9 Lab 2.1 - Fetching Data .................................................................................... 2-12 Lab 2.2 - Converting Queries ........................................................................... 2-15 Caching ............................................................................................................ 2-17 Locking ............................................................................................................. 2-23 Lab 2.3 - Reducing Locking .............................................................................. 2-25 Temporary Tables ............................................................................................ 2-27 Lab 2.4 - Temporary Tables ............................................................................. 2-28 InitFrom ............................................................................................................ 2-31 Parm Tables ..................................................................................................... 2-31 Date Effectiveness ........................................................................................... 2-32 Computed Columns in Views ........................................................................... 2-34 Data Integration ................................................................................................ 2-36 Lab 2.5 - Integrating External Data................................................................... 2-44 Summary .......................................................................................................... 2-46 Test Your Knowledge ....................................................................................... 2-47 Quick Interaction: Lessons Learned ................................................................. 2-50 Solutions ........................................................................................................... 2-51
Chapter 3: Classes
3-1
Objectives ........................................................................................................... 3-1 Introduction ......................................................................................................... 3-1 Collection Classes .............................................................................................. 3-2 Lab 3.1 - Create a Map .................................................................................... 3-12 Application Object Classes ............................................................................... 3-14 Lab 3.2 - Create a Query From Code ............................................................... 3-19
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
i
Development III in Microsoft Dynamics® AX 2012 Application Substituted Kernel Classes ............................................................ 3-21 Lab 3.3 - Create a Global method .................................................................... 3-24 RunBase Framework ........................................................................................ 3-25 Lab 3.4 - Make a RunBase Class ..................................................................... 3-37 Args Object ....................................................................................................... 3-39 Lab 3.5 - Using Args ......................................................................................... 3-42 Summary .......................................................................................................... 3-44 Test Your Knowledge ....................................................................................... 3-45 Quick Interaction: Lessons Learned ................................................................. 3-47 Solutions ........................................................................................................... 3-48
Chapter 4: Forms
4-1
Objectives ........................................................................................................... 4-1 Introduction ......................................................................................................... 4-1 Architecture ........................................................................................................ 4-2 Data Sources ...................................................................................................... 4-6 Lab 4.1: Create a form ..................................................................................... 4-16 Form Controls ................................................................................................... 4-18 Lab 4.2 - Use Unbound Controls ...................................................................... 4-22 Form Methods .................................................................................................. 4-23 Lab 4.3 - Initialize a Form ................................................................................. 4-25 Placement of Code ........................................................................................... 4-26 Additional Controls ........................................................................................... 4-26 Lab 4.4 - Add a window control ........................................................................ 4-35 Summary .......................................................................................................... 4-37 Test Your Knowledge ....................................................................................... 4-38 Quick Interaction: Lessons Learned ................................................................. 4-39 Solutions ........................................................................................................... 4-40
Chapter 5: Visual Studio Integration
5-1
Objectives ........................................................................................................... 5-1 Introduction ......................................................................................................... 5-1 Application Explorer ........................................................................................... 5-2 Visual Studio Projects ........................................................................................ 5-3 Managed code projects ...................................................................................... 5-3 Deploying Managed Code .................................................................................. 5-6 Visual Studio Debugging Experience for X++ .................................................... 5-7 Lab 5.1 - Create a Managed Code Project ......................................................... 5-9 Lab 5.2 - Create an Event Handler in Managed Code ..................................... 5-10 Summary .......................................................................................................... 5-11 Test Your Knowledge ....................................................................................... 5-12 Quick Interaction: Lessons Learned ................................................................. 5-14 Solutions ........................................................................................................... 5-15
Chapter 6: Workflow
6-1
Objectives ........................................................................................................... 6-1 Introduction ......................................................................................................... 6-1 Workflow Configuration ...................................................................................... 6-2 Create a Workflow Category .............................................................................. 6-2 Create a Query ................................................................................................... 6-3
ii
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Table of Contents Create a Workflow Type ..................................................................................... 6-4 Enable Workflow on a Form ............................................................................... 6-5 Create a Workflow Approval ............................................................................... 6-8 Create Event Handlers ....................................................................................... 6-9 Author a Workflow ............................................................................................ 6-12 Lab 6.1 - Add Another Condition to the Submit Action ..................................... 6-15 Lab 6.2 - Enable Resubmit ............................................................................... 6-17 Summary .......................................................................................................... 6-20 Test Your Knowledge ....................................................................................... 6-21 Quick Interaction: Lessons Learned ................................................................. 6-22 Solutions ........................................................................................................... 6-23
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
iii
Development III in Microsoft Dynamics® AX 2012
iv
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Introduction
INTRODUCTION Welcome We know training is a vital component of retaining the value of your Microsoft Dynamics® AX 2012. investment. Our quality training from industry experts keeps you up-to-date on your solution and helps you develop the skills necessary for fully maximizing the value of your solution. Whether you choose Online Training, Classroom Training, or Training Materials; there is a type of training to meet everyone's needs. Choose the training type that best suits you so you can stay ahead of the competition.
Online Training Online Training delivers convenient, in-depth training to you in the comfort of your own home or office. Online training provides immediate access to training 24 hours-a-day. It is perfect for the customer who does not have the time or budget to travel. Our newest online training option, eCourses, combine the efficiency of online training with the in-depth product coverage of classroom training, with at least two weeks to complete each course.
Classroom Training Classroom Training provides serious, in-depth learning through hands-on interaction. From demonstrations to presentations to classroom activities, you receive hands-on experience with instruction from our certified staff of experts. Regularly scheduled throughout North America, you can be sure you will find a class convenient for you.
Training Materials Training Materials enable you to learn at your own pace, on your own time with information-packed training manuals. Our wide variety of training manuals feature an abundance of tips, tricks, and insights you can refer to again and again:
Microsoft Dynamics Courseware The Microsoft Dynamics Courseware consists of detailed training manuals, designed from a training perspective. These manuals include advanced topics as well as training objectives, exercises, interactions and quizzes. Look for a complete list of manuals available for purchase on the Microsoft Dynamics website: www.microsoft.com/Dynamics.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
0-1
Development III in Microsoft Dynamics® AX 2012
Microsoft Dynamics Courseware Contents Test Your Skills Within the Microsoft Dynamics Training Materials you find a variety of different exercises. These exercises are offered in three levels to accommodate the variety of knowledge and expertise of each student. We suggest you try the level three exercises first, if you need help completing the task look to the information in the level two exercises. If you need further assistance each step of the task is outlined in the level one exercise.
Challenge Yourself! Level 3 exercises are the most challenging. These exercises are designed for the experienced student who requires little instruction to complete the required task.
Need a Little Help? Level 2 exercises are designed to challenge students, while providing some assistance. These exercises do not provide step by step instructions, however, do provide you with helpful hints and more information to complete the exercise.
Step by Step Level 1 exercises are geared towards new users who require detailed instructions and explanations to complete the exercise. Level 1 exercises guide you through the task, step by step, including navigation.
Quick Interaction: Lessons Learned At the end of each chapter within the Microsoft Dynamics Training Material, you find a Quick Interaction: Lessons Learned page. This interaction is designed to provide the student with a moment to reflect on the material they have learned. By outlining three key points from the chapter, the student is maximizing knowledge retention, and providing themselves with an excellent resource for reviewing key points after class.
0-2
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Introduction
Documentation Conventions The following conventions and icons are used throughout this documentation to help you quickly and effectively navigate through the information. CAUTION: Cautions are found throughout the training manual and are preceded by the word CAUTION in bold. Cautions are used to remind you of a specific result of a specific action which may be undesirable. HINT: Hints are found throughout the training manual and are preceded by the word HINT in bold. Hints are used to suggest time-saving features or alternative methods for accomplishing a specific task. NOTE: Notes are found throughout the training manual and are preceded by the word NOTE in bold. Notes are used to provide information which, while not critical, may be valuable to an end user. BEYOND THE BASICS: Advanced information found throughout the training manual is preceded by the words BEYOND THE BASICS in bold. Beyond the Basics provides additional detail, outside of standard functionality, that may help you to more optimally use the application. EXAMPLE: Examples are found throughout the training manual and are preceded by the word EXAMPLE in bold. Examples bring to light business scenarios that may better explain how an application can be used to address a business problem.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
0-3
Development III in Microsoft Dynamics® AX 2012
Student Objectives What do you hope to learn by participating in this course? List three main objectives below. 1.
2.
3.
0-4
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 1: X++ Unit Test Framework
CHAPTER 1: X++ UNIT TEST FRAMEWORK Objectives The objectives are: •
Create a test case.
•
Add methods to a test case.
•
Run a test case.
•
Build a test project and suite.
•
Isolate test cases appropriately.
Introduction The X++ Unit Test framework allows for unit tests to be created along with the code they are designed to test. A unit test is code that verifies that some specific application code has been implemented correctly. If you adhere to the principles of test-driven development, it is best for the developer who is writing the application code to write the unit tests either before or during development.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
1-1
Development III in Microsoft Dynamics® AX 2012 Scenario Isaac, the systems developer, is about to start a development project based on some written specifications. Before he begins writing code, he has been asked to create a Unit Test suite, to test the project during its lifecycle.
Creating Test Cases A unit test, in the context of the Unit Test framework, includes test cases, how test cases are staged with data, and the organization of test cases. A test case is a class that extends the SysTestCase class. You can add test methods to test each requirement of the feature code. The following example is of how to create a test case. In this example you will be testing an existing system class, so the results should be successful. You will test the SysDictTable class, to make sure its methods to return the table name and group for a given table name, are correct. The following is the test case class declaration. [SysTestTargetAttribute('SysDictTable', UtilElementType::Class)] class SysDictTableTest extends SysTestCase { SysDictTable sysDictTable; }
Note the attribute used on the class. The SysTestTargetAttribute attribute is attached to the test case to specify which element is being tested. In this case, it is the SysDictTable class being tested. There are a number of predefined attributes available in the Unit Test framework:
1-2
Attribute
Description
Applied to
SysTestMethodAttribute
Indicates that a method is a unit test.
Method
SysTestCheckInAttribute
Indicates the test is a check-in unit test. A check-in test is run when checking in code to a version control system to ensure a proper level of quality.
Method or Class
SysTestNonCheckInAttribute
Indicates the test is not a check-in test.
Method
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 1: X++ Unit Test Framework Attribute
Description
Applied to
SysTestTargetAttribute
Indicates the application object that is being tested by the case. This attribute takes two parameters: Name of element, and UtilElementType value.
Class
SysTestInactiveTestAttribute
Indicates the class or method is inactive.
Method
Another variable has been created in the ClassDeclaration, of the type of the class being tested.
Adding Methods to Test Cases The next step in creating a test case, is to add some methods to the test case class. Each method should assert that one of the known requirements is true. There are two new methods added in the following example: one to assert that the table's name is as expected, and the other to assert that the table's group value is as expected. [SysTestMethodAttribute] public void testTableName() { // Verify that the tables name is set correctly. this.assertEquals("CustTable", sysDictTable.name()); } [SysTestMethodAttribute] public void testTableGroup() { // Verify that the tables group is set correctly. this.assertEquals(TableGroup::Main, sysDictTable.tableGroup()); }
A test method will typically contain several assertions that are required to hold true, for the test to be successful. Note the use of the assertEquals method. This method, and other assert methods, are available as part of the SysTestCase framework. These methods also have an optional string parameter called _message, to specify the message that would go into the infolog, if the assertion fails. The following is a list of available methods: Method
Description
assertEquals
Tests if two values are equal.
assertNotEqual
Tests if two values are different.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
1-3
Development III in Microsoft Dynamics® AX 2012 Method
Description
assertTrue
Tests if the value is true.
assertFalse
Tests if the value is false.
assertNull
Tests if the value is null.
assertNotNull
Tests if the value is not null.
assertSame
Tests if the objects referenced are the same.
assertNotSame
Tests if the objects referenced are not the same.
assertRealEquals
Tests if real values differ by the amount specified in the delta.
assertExpectedInfolog Message
Tests for an infolog message that is expected.
fail
Enables a developer to create custom validation logic and then call the fail method to integrate with the Unit Test framework.
Setup Method The next step is to override the setUp method of the SysTestClass. This will tell the Unit Test framework what parameters need to be setup before the test is run. In this case, you need to instantiate the SysDictTable object using the table name. public void setUp() { sysDictTable = new SysDictTable(TableNum(CustTable)); super(); }
The setUp method can also be used to insert or update any data necessary for the test.
TearDown Method At the completion of a test run, it may be necessary to "undo" any data created in the setUp method. This can be done in the tearDown method, which is called at the end of the test. The tearDown method can be overridden on any class extending SysTestCase.
1-4
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 1: X++ Unit Test Framework
Running Test Cases When the test class has methods added to it, and appropriate setUp and tearDown methods, it can be run. To run the test case, right click the SysDictTableTest class, click Add-Ins and then Run tests. The Unit Test toolbar will appear, and the test case will be added to the list of tests. The test will automatically be run, and the results displayed in the toolbar, and in the infolog (if it failed).
FIGURE 1.1 UNIT TEST TOOLBAR
The SysDictTableTest test should pass on the first run. Try changing the expected values in the test* methods, or the table id used in the setUp method, to emulate a test that fails, then run the test again. To run the test again, click the Run button on the Unit Test toolbar. If the test fails, click the Details button to see more details of the results.
Reviewing Test Results To review test results, the Test jobs form can be opened from the Tools menu (Tools > Unit test > Test jobs). The Test jobs form lists all previous runs of tests, and displays information about the test.
FIGURE 1.2 TEST JOBS FORM
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
1-5
Development III in Microsoft Dynamics® AX 2012 Click the Tests button on the Test jobs form, to open another form where test information is available at the method level.
FIGURE 1.3 TEST FORM SHOWS INFORMATION AT THE METHOD LEVEL
1-6
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 1: X++ Unit Test Framework Listeners Test results can be displayed in various formats, to suit the requirement. There are several standard listeners that capture test results and format their results in different ways. These can be configured on the Unit Test Parameters window, as seen in the figure below.
FIGURE 1.4 LISTENER CONFIGURATION
Standard listeners include: Database, Infolog, Infolog (result only), Message window, Print window, Progress bar, Text file, and XML file. NOTE: It is also possible to create new listeners, by extending the SysTestListener interface. This can be explored further on MSDN at this location http://go.microsoft.com/fwlink/?LinkID=225151&clcid=0x409,by following the "How to: Display Test Case Results" topic in the "Unit Test Framework" section.
Build Test Projects and Suites Test classes can be organized in two ways: •
Test projects
•
Test suites
Test Projects Test projects are groupings of test classes and appear in the Development Project tree along with other project types.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
1-7
Development III in Microsoft Dynamics® AX 2012 Use the following procedure to create a new Test project: 1. Open the Project tree. 2. Right-click either the Private or Shared node, point to New, and then click Test project. This creates a project with a test suite. 3. Open the AOT. 4. In the AOT, select one or more test classes to include in the test project. Drag them to the TestSuite node in the Test project.
FIGURE 1.5 TEST PROJECT
Once test classes have been added to a Test project, the entire collection of tests can be run at the same time, by right-clicking the root node of the project, and clicking Run.
Test Suites Collections of tests can also be created with X++ code. These collections extend the SysTestSuite class, and are referred to as Suites. Use the following procedure to create a test suite class: 1. In the AOT, create a new Class. 2. Open the new class in the Code Editor. 3. Edit the ClassDeclaration so that the class extends the SysTestSuite class. 4. Override the new method on the class. 5. In the new method, call the add method to add test cases to the suite, as shown. public void new() { // Create an instance of a test suite. SysTestSuite suiteDictTableTest = new SysTestSuite(classstr(SysDictTableTest)); ; super();
1-8
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 1: X++ Unit Test Framework this.add(suiteDictTableTest); }
The setUp and tearDown methods can also be used in the scope of a Test Suite. Both methods are available to override on any class extending the SysTestSuite class. In this way, the setting up of data and variables can be done before the entire suite of tests is run.
Isolation The isolation level of a test case varies based on the changes that the test case will make to the data. Each test case could have different needs for isolation based on what data it will change. The Unit Test framework provides four test suite base classes that provide different levels of isolation. The following table describes the test suites by the level of isolation that they provide. Test suite class
Description
SysTestSuite
Default test suite. No isolation.
SysTestSuiteCompanyIsolate Class
Constructs an empty company account for each test class. All test methods on the class are run within the company, then it is deleted.
SysTestSuiteCompanyIsolate Method
Constructs an empty company account for each test method, and then deletes it at the end of the method.
SysTestSuiteTTS
Wraps each test method in a transaction. After the method is complete, the transaction is aborted. Note: Tests that need to commit data will not work in this suite. Also, the ParmExceptionExpected exception is not supported in this suite.
SysTestSuiteCompIsolate ClassWithTts
This is a combination of SysTestSuiteCompanyIsolateClass and SysTestSuiteTTS.
To apply a specific test suite to a test class, override the createSuite method on the test class, and return an object of the type of the suite required.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
1-9
Development III in Microsoft Dynamics® AX 2012 The following example demonstrates how a test suite can be applied to a test class. class SysTestSuite createSuite() { // Isolation level: construct a company account for the entire test class. return new SysTestSuiteCompanyIsolateClass(this); }
1-10
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 1: X++ Unit Test Framework
Lab 1.1 - Create a Test Case During this lab, you will create a test case and add it to a test suite. Scenario Isaac is about to start a development project based on some written specifications. Before he begins writing code, he has been asked to create a Unit Test suite, to test the project during its lifecycle.
Challenge Yourself! Create a test case class, and add a test method to it. The test case should check that when an address record's Zip code is changed, the correct City is automatically updated. Once complete, create a test suite class that enforces TTS isolation, and link the test case class to it. Finally, run the test.
Step by Step 1. 2. 3. 4.
Open the AOT. Create a new class. Rename the class to MyTestSuite. Edit the ClassDeclaration so that it extends the SysTestCase class, and add a variable logisticsPostalAddress of type LogisticsPostalAddress. 5. Add a public method called testCity. 6. Add a line of code to the method: this.assertEquals("New York", logisticsPostalAddress.City); 7. Override the setUp method on the class. It should look like the following code. public void setUp() { ttsBegin; select firstonly forupdate logisticsPostalAddress where logisticsPostalAddress.City != "New York" && logisticsPostalAddress.CountryRegionId == "USA"; logisticsPostalAddress.validTimeStateUpdateMode(ValidTimeSt ateUpdate::EffectiveBased); logisticsPostalAddress.ZipCode = "10001"; logisticsPostalAddress.ZipCodeRecId = 0; logisticsPostalAddress.LogisticsPostalAddressMap::modifiedF ieldZipCode();
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
1-11
Development III in Microsoft Dynamics® AX 2012 logisticsPostalAddress.update(); ttsCommit; super(); }
8. 9. 10. 11.
Create a new class. Name the class MyTestSuiteTTS. Edit the ClassDeclaration so that it extends SysTestSuiteTTS. Go back to the MyTestSuite class, and override its createSuite method. 12. Change the method to read: return new MyTestSuiteTTS(this); 13. Run the test class.
1-12
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 1: X++ Unit Test Framework
Summary This lesson explains how to use the Unit Test framework to test X++ code during the development lifecycle. The concepts of test cases, assertions, projects, suites and isolation are introduced, demonstrating the rich features available to build robust logic tests.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
1-13
Development III in Microsoft Dynamics® AX 2012
Test Your Knowledge Test your knowledge with the following questions. 1. What class does a test class need to extend?
2. What is the setUp method on the SysTestCase class used for?
1-14
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 1: X++ Unit Test Framework 3. Where can the Test jobs form be opened from?
4. What is meant by "isolation" in the context of the Unit Test framework?
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
1-15
Development III in Microsoft Dynamics® AX 2012
Quick Interaction: Lessons Learned Take a moment and write down three key points you have learned from this chapter 1.
2.
3.
1-16
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 1: X++ Unit Test Framework
Solutions Test Your Knowledge 1. What class does a test class need to extend? MODEL ANSWER: SysTestCase 2. What is the setUp method on the SysTestCase class used for? MODEL ANSWER: It is used to prepare variables and data that are required for the test. 3. Where can the Test jobs form be opened from? MODEL ANSWER: Tools > Unit test > Test jobs. 4. What is meant by "isolation" in the context of the Unit Test framework? MODEL ANSWER: Isolation refers to the scope in which company data should be created and destroyed during a test run.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
1-17
Development III in Microsoft Dynamics® AX 2012
1-18
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data
CHAPTER 2: WORKING WITH DATA Objectives The objectives are: •
Program optimal database access using a "while select" statement.
•
Program optimal database access using queries.
•
Describe the caching mechanisms in Microsoft Dynamics® AX.
•
Prevent and resolve database locking.
•
Use temporary tables in classes, forms, and reports.
•
List the reasons for using InitFrom methods.
•
Use ParmId and ParmTables.
•
Discuss date effectiveness and describe how to build date effective forms.
•
Add a computed column to a view.
•
Employ the various techniques available for integrating external data with Microsoft Dynamics AX.
Introduction A Microsoft Dynamics AX application processes large amounts of data. Most functions involve sending data between the client and Application Object Server (AOS) and between the AOS and database server. It is important to use the correct approach to database access when developing in Microsoft Dynamics AX. Almost every performance bottleneck is associated with database traffic.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-1
Development III in Microsoft Dynamics® AX 2012
While Select This section describes the different qualifiers and options that can be used in the select statement, to achieve optimal database access performance. The complete syntax for the select statement is as follows.
[while] select [reverse] [firstfast] [firstonly] [firstOnly10] [firstOnly100] [firstOnly1000] [forupdate] [nofetch] [crosscompany] [forcelitterals | forceplaceholders] [forcenestedloop] [forceselectorder] [repeatableRead] [validTimeState] [ * | from] [ index [hint] ] [ group by {} ] [ order by { [asc][desc]} ] [ where ] [ outer | exists | notexists ] join [reverse] [ * | from] [ index ] [sum] [avg] [minof] [maxof] [count] [ group by {} ] [ order by { [asc][desc]} ] [ where ] ] ::= | , ::= fieldname | ()
General Optimization To optimize general performance, the following tools and keywords may be used.
Fieldlist One way to optimize communication with the database is to specify which fields are returned. For example, for a table with 40 fields, reading the information from only four fields will reduce the amount of data sent from the database server by up to 90 percent.
2-2
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data The following illustrates using a field list. while select amountMST from ledgerTrans { amountMST += ledgerTrans.amountMST; }
NOTE: Use this optimization with care. If the record returned from the database is subsequently passed as a parameter to other methods, that method may have been written on the assumption that all fields are set. Only use field lists when controlling access to the information locally.
Aggregation To obtain a sum of records, consider instructing the database to calculate the sum and only return the result, as an alternative to reading all the records and making the aggregation yourself. To receive a sum specified for one or more fields in the table, combine the aggregation with a group by clause. The following aggregation clauses are available. Aggregation clause
Description
sum
Returns the sum of the values in a field.
avg
Returns the average of the values in a field.
maxof
Returns the maximum of the values in a field.
minof
Returns the minimum of the values in a field.
count
Returns the number of records that satisfy the statement.
The following illustrates using aggregate functions: select sum(qty) from inventTrans; qty = inventTrans.amountMST; select count(recId) from inventTrans; countInventTrans = ledgerTrans.recId;
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-3
Development III in Microsoft Dynamics® AX 2012 Join To read records in a main table and then process related records in a transaction table for each main table record, one solution is to make one "while select" statement which loops over the records in the main table and another nested "while select" statement which loops over the transaction records related to the current record in the main table. The following is an example of a nested "while select" statement while select InventTable { while select InventTrans where InventTrans.itemId == inventTable.itemId { qty += inventTrans.qty; } }
To process 500 records in the main table, this approach would have 501 SQL statements executed on the database. Alternatively, making a single "while select" statement with a join clause reduces the number of SQL statements to just 1. The following example illustrates using a join clause (and fieldlists for extra performance). while select recId from inventTable join qty from inventTrans where inventTrans.itemId == inventTable.itemId { qty += inventTrans.qty; }
ForceLiterals ForceLiterals instructs the kernel to reveal the actual values used in the "where" clauses to the database server at the time of optimization. This is the default behavior in all "join" statements involving more than one table from the following table groups:
2-4
•
Miscellaneous
•
Main
•
Transaction
•
Worksheet Header
•
Worksheet Line
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data The advantage of using this keyword is that the server now gets all information needed to calculate the optimal access plan for a statement. The disadvantage is that the access plan cannot be reused with other search values and that the optimization may use more CPU resources on the database server. High frequency queries should not use literals. The following X++ statement is an example of how to use this keyword. static void DemoForceLiterals() { InventTrans inventTrans; ; while select forceliterals inventTrans order by itemId where inventTrans.DatePhysical >= mkdate(21,12,2012) { } }
It is not possible to determine whether an index on itemId or an index on DatePhysical should be used without considering the actual value of 21\12\2012. Therefore, the keyword should be used as shown in the previous code sample.
ForcePlaceholders ForcePlaceholders instructs the kernel not to reveal the actual values used in where clauses to the database server at the time of optimization. This is the default in all non-join statements. The advantage of using this keyword is that the kernel can reuse the access plan for other similar statements with other search values. The disadvantage is that the access plan is computed without considering that data distribution might not be even, or that the access plan is an "on average" access plan. The following X++ statement is an example of when to use this keyword. static void DemoForcePlaceholders() { SalesTable salesTable; SalesLine salesLine; ; while select forcePlaceholders salesLine join salesTable where salesTable.SalesId == salesLine.SalesId && salesTable.SalesId == '10' { } }
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-5
Development III in Microsoft Dynamics® AX 2012 In the previous code example, the database server automatically chooses to search the SalesTable using an index on salesId. The database server uses the fact that the salesId column is a unique field and does not need the actual search value to compute the optimal access plan.
FirstFast FirstFast instructs the SQL-database to prioritize fetching the first few rows fast over fetching the complete result set. This also means that the SQL-database might select an index fitting the order by clause over an index fitting the "where" clause. The FirstFast hint is automatically issued from all forms, but is rarely used directly from X++.
Firstonly When Microsoft Dynamics AX fetches data from the database, it transfers a package of records in each fetch. This is called read-ahead caching and is performed to minimize calls to the database. If it is known that only one record will be fetched, you can disable the read-ahead caching with this qualifier. NOTE: It is best practice to use this in the "find" methods on the tables. The following example illustrates the use of FirstOnly. static CustTable findCustTable(CustAccount _custAccount) { CustTable custTable; ; select firstonly custTable where custTable.AccountNum == _custAccount; return custTable; }
NOTE: It is important that "find" methods are designed to be executed on the tier where the method is called. This is the default behavior on static table methods. If the method is bound on the server it will always require a round-trip to the AOS even if the data was cached on the client.
Access Plan Repair The following keywords are categorized as access plan repair keywords and should not be used unless required to fix specific performance problems.
Index or Index Hint Keywords This keyword can be used either in the form index or index hint . If only index is used, Microsoft Dynamics AX generates an order by clause that corresponds to the index components.
2-6
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data An incorrectly used index hint can affect performance, so index hints should only be applied to SQL statements that do not have dynamic where or order by clauses and where the effect of the hint can be verified. Microsoft Dynamics AX automatically removes index hints referring to a disabled index. By default index hints are disabled on the AOS.
ForceSelectOrder This keyword forces the database server to access the tables in a join in the given order. If two tables are joined the first table in the statement is always accessed first. This keyword is frequently combined with the forceNestedLoop keyword. One situation where it can be interesting to force a select order is when you use index hint on a join. The following construction is an example of the ForceSelectOrder. static void DemoForceSelectOrder() { InventTrans inventTrans; InventDim inventDim; ; while select inventTrans index hint ItemIdx where inventTrans.ItemId == 'X' join inventDim where inventDim.inventDimId == inventTrans.inventDimId && inventDim.inventBatchId == 'Y' { }
}
Give the database a hint on using the index ItemIdx on the table InventTrans. This works well if the database searches this table first. But if the database, with the help of the generated statistics, starts with the table InventDim and then finds records in InventTrans for each occurrence of InventDimId, the use of the index ItemIdx may not be an appropriate approach. To hint indexes in a join, consider specifying forceSelectOrder, as shown in the following example. static void DemoForceSelectOrder() { InventTrans inventTrans; InventDim inventDim; ; while select forceSelectOrder inventTrans index hint ItemIdx where inventTrans.ItemId == 'X' join inventDim
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-7
Development III in Microsoft Dynamics® AX 2012 where inventDim.inventDimId == inventTrans.inventDimId && inventDim.inventBatchId == 'Y' { } }
ForceNestedLoops This keyword forces the database server to use a nested-loop algorithm to process a given SQL statement that contains a join. This means that a record from the first table is fetched before trying to fetch any records from the second table. Generally other join algorithms like hash-joins, merge-joins, and others are also considered. This keyword is frequently combined with the forceSelectOrder keyword. Review the previous example with the tables InventTrans and InventDim. You could risk that the database finds all InventTrans records by the index ItemIdx and all the InventDim records by the BatchId. (If you hint the index DimIdIdx this will be used for this search.) The two collections of records are hashed together. For the database to find the inventTrans and then the inventDim for each inventTrans, specify forceNestedLoops, as shown in the following example. static void DemoForceSelectOrder() { InventTrans inventTrans; InventDim inventDim; ; while select forceSelectOrder forceNestedLoop inventTrans index hint ItemIdx where inventTrans.ItemId == 'X' join inventDim where inventDim.inventDimId == inventTrans.inventDimId && inventDim.inventBatchId == 'Y' { }
2-8
}
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data Cross Company By default, select statements in X++ adhere to the DataAreaId structure of the application. For example, if there are two company accounts, 001 and 002, a select statement run in company 002 will only return records stamped with dataAreaId equal to 002. The crossCompany keyword allows the statement to fetch records regardless of the dataAreaId. The following example shows a statement that will count all customer records from all company accounts. select crossCompany count(recId) from custTable; countCustTable = custTable.recId;
You also have the option of adding a container variable of company identifiers immediately after the crosscompany keyword (separated by a colon). The container restricts the selected rows to those with a dataAreaId that match a value in the container. The following example illustrates the use of the crossCompany keyword. CustTable custTable; container conCompanies = [ '001', '002', 'dat' ]; ; while select crosscompany : conCompanies * from custTable order by dataAreaId { //do something }
Query A query is an object-oriented interface to the SQL database. A query is composed of objects from different classes. Various objects are available to manipulate a query object from the AOT. Use the queryRun object to execute the query and fetch data. The query object is the definition master. It has its own properties and has one or more related data sources. The queryBuildDataSource defines access to a single table in the query. If one data source is added below another data source, they form a join between the two tables. The queryBuildFieldList object defines which fields to fetch from the database. The default is a dynamic field list that is equal to a "select * from …". Each data source has only one queryBuildFieldList object which contains information about all selected fields. You can also specify aggregate functions like sum, count, and avg with the field list object.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-9
Development III in Microsoft Dynamics® AX 2012 The queryBuildRange object contains a limitation of the query on a single field. The queryFilter object is used to filter the result set of an outer join. It filters the data at a later stage than the queryBuildRange object and filters the parent table based on the child table results. The queryBuildDynaLink objects can only exist on the outer data source of a query. The objects contain information about a relation to an external record. When the query is run, this information is converted to additional entries in the "where" section of the SQL statement. The function is used by forms when two data sources are synchronized. The subordinate data source contains DynaLink(s) to the master data source. The function is used even if the two data sources are placed in two different forms, but are synchronized. The queryBuildLink objects can only exist on inner data sources. The objects specify the relation between the two tables in the join. The following is a sample of code using some of these QueryBuild objects, to build a query and loop through it: Query q; QueryRun qr; QueryBuildDatasource QueryBuildRange ;
qbds1, qbds2; qbr;
q = new Query(); qbds1 = q.addDataSource(tablenum(InventTable)); qbds2 = qbds1.addDataSource(tablenum(InventTrans)); qbds2.relations(TRUE); //this enforces a relationship between this datasource and its parent. Relationships defined in the Data Dictionary are used by default. qbr = qbds1.addRange(fieldnum(InventTable, ItemId)); qbr.value(SysQuery::value("1706")); //SysQuery object provides various static methods to assist in defining Query criteria. The SysQuery::value() method should always be used when defining a singular value for a range. qr = new QueryRun(q); while(qr.next()) { //do something }
You can build a query in the AOT using MorphX or as shown in the previous topic, by dynamically creating the query by X++ code. Both approaches are used in the standard application. One advantage of making the query dynamic is that it is not public in the AOT and is protected against unintentional AOT changes. Alternatively, an advantage of creating the query in the AOT is that it can be reused in various places, saving lines of identical code, and making widereaching query adjustment easier.
2-10
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data Forms and reports use queries for fetching data. These queries are not public below the Query-node in the AOT. Forms generate the queries dynamically from the setting of the data source, whereas Reports have an integrated query-node. In both cases, you can edit the query for modification. There are alternative ways of specifying existing objects in a query. Best practice is to use the intrinsic functions which use table and field ID as arguments (tableNum and fieldNum).
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-11
Development III in Microsoft Dynamics® AX 2012
Lab 2.1 - Fetching Data Scenario As part of a larger modification, you need to develop code which queries the database for specific records. Use AOT queries, "while select" statements, and query objects to develop the most efficient data fetching code.
Challenge Yourself! Make the following query "Count the number of customers from all companies, limited to a specific currency." For this example, make USD the limiting value. Perform the following actions: 1. Create a job which makes the above select using "while select". The currency to use can be defined in the code. 2. Create a new query in the AOT. Prompt the user for the currency to use when the query is run. 3. Create a job which executes the query defined in step 2. 4. Create a new job which builds the same query dynamically using the query classes. Prompt the user for the currency to use when the query is run. 5. Verify that the three implementations return the same data.
Step by Step 1. The following shows query made in a job using "while select". static void SelectCustomerJob(Args _args) { CustTable custTable; Counter recordsFound; ; while select crosscompany custTable where custTable.Currency =="USD" { recordsFound++; } info(strFmt("Customers found: %1", recordsFound)); }
2-12
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data 2. The following shows a query built in AOT:
FIGURE 2.1
3. The following shows the job that executes query from step 2. static void SelectCustomerRunQuery(Args _args) { QueryRun queryRun; Counter recordsFound; ; queryRun = new QueryRun(queryStr(CustTableByCurrency));
}
if (queryRun.prompt()) { while (queryRun.next()) { recordsFound++; } } info(strFmt("Customers found: %1", recordsFound));
4. The following shows the job that dynamically builds the query static void SelectCustomerQuery(Args _args) { Query query; QueryBuildDataSource queryBuildDatasource; QueryFilter queryFilter; QueryRun queryRun; Counter recordsFound; ; query = new Query(); query.allowCrossCompany(TRUE); queryBuildDataSource = query.addDataSource(tableNum( CustTable)); queryFilter = query.addQueryFilter(queryBuildDataSource,
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-13
Development III in Microsoft Dynamics® AX 2012 identifierStr(Currency)); queryRun = new QueryRun(query); if (queryRun.prompt()) { while (queryRun.next()) { recordsFound++; } } info(strFmt("Customers found: %1", recordsFound)); }
2-14
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data
Lab 2.2 - Converting Queries Scenario Your development manager has asked that you re-write all your queries using Query objects instead of "while select" statements. Make a job that dynamically builds a query, using query objects, with the same structure as the following statement. inventTrans
inventTrans;
inventDim inventDim;
while select sum(qty) from inventTrans where inventTrans.ItemId =="1706" join inventDim group by inventBatchId where inventDim.InventDimId == inventTrans.InventDimId { info(strFmt("Batch %1, qty %2", inventDim.inventBatchId, inventTrans.qty)); }
Enable the user to enter the item id when the query is run. Perform the following actions: 1. Create a new job. 2. Use the Query, QueryBuildDataSource classes, and more, to create a query dynamically. 3. Create another job that uses "while select" as shown. 4. Verify that the two implementations return the same data, using item number 1706.
Step by Step 1. 2. 3. 4. 5. 6.
In the AOT, create a new job. Copy the code above in to the job. Press F5 to run the code Note the result. Create another new job. Add the following code.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-15
Development III in Microsoft Dynamics® AX 2012 static void InventTransBuildQuery(Args _args) { Query query; QueryBuildDataSource queryBuildDataSource; QueryBuildRange queryBuildRange; QueryRun queryRun; Qty total; InventTrans inventTrans; inventDim inventDim; query = new Query(); queryBuildDataSource = query.addDataSource(tableNum(InventTrans)); queryBuildDataSource.addSelectionField(fieldNum(InventTrans ,Qty),SelectionField::Sum); queryBuildRange = queryBuildDataSource.addRange(fieldNum(InventTrans, ItemId)); queryBuildDataSource = queryBuildDataSource.addDataSource(tableNum(InventDim)); queryBuildDataSource.addGroupByField(fieldNum(InventDim, InventBatchId)); queryBuildDataSource.relations(true); queryRun = new QueryRun(query); if (queryRun.prompt()) { while (queryRun.next()) { inventTrans = queryRun.get(tableNum(InventTrans)); inventDim = queryRun.get(tableNum(inventDim)); info(strFmt("Batch %1, qty %2", inventDim.inventBatchId, inventTrans.qty)); }
}
}
7. Press F5 to run the job. 8. In the prompt, enter 1706 in the Item number field 9. Note the result.
2-16
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data
Caching The previous sections have discussed how to optimize communication with the database by structuring SQL statements that make the database access data in the correct way. But the most optimal strategy is to have no communication with the database or to minimize the number of communications with the database server. The concept of caching is to remember the information retrieved from the database and use this memory when the same data is needed at a later time. However, this strategy has one large drawback, if the remembered information is no longer valid, this could compromise the consistency of the database, as the updates will be made based on invalid data. This section covers data caching done by Microsoft Dynamics AX AOS and/or client. Microsoft Dynamics AX supports the following kinds of caching: •
Read-ahead caching
•
Single-record caching
•
Entire table caching
•
Record view caching
•
Display method caching
•
Global object cache
•
Date effective join cache
•
Unique index cache extension
•
Cache lookup for table groups
The remainder of this section explains the first five above mentioned data catching.
Read-ahead Caching The SQL interface in Microsoft Dynamics AX provides read-ahead caching and uses a buffer to pre-fetch rows. The read-ahead adapts to the queries executed. As an example, a query to show rows in a form pre-fetches in chunks that correspond to the number of rows currently visible in the form, whereas a multirow select with the FirstOnly keyword will not pre-fetch.
Single-Record Caching Microsoft Dynamics AX offers record caching. Rows selected through a cachekey are cached and successive look-ups specifying the cache-key are then served from the record cache. The record-cache holds the rows that match frequently issued single-row queries. The cache-key is the Primary Key on the cached table. If a Primary Key does not exist, the first unique index is used.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-17
Development III in Microsoft Dynamics® AX 2012 Because only single-row queries go into the cache, the cache is small with a default value of 100 rows on the client and 2000 rows on the AOS. Four single record cache settings are used with tables: CacheLookup
Result
None
No data is cached or retrieved from cache for this table. No caching should be used for tables that are heavily updated or where it is unacceptable to read outdated data.
NotInTTS
All successful caching key selects are cached. When in TTS (after ttsbegin), the record is read once from the database and subsequently from cache. The record is select-locked when read in TTS which ensures that the record cached is not updated as long as the TTS is active. A typical example of its use is the CustTable in the standard application. It is acceptable to read outdated data from cache outside TTS, but when data is used for validation or creating references it is ensured that it is real-time data.
Found
All successful caching key selects are cached. All caching key selects are returned from cache if the record exists there. A select forupdate in TTS forces reading from the database and replaces the record in the cache. This is typically used for static tables like ZipCodes where the normal case is that the record exists.
FoundAndEmpty
All selects on caching keys are cached, even selects not returning data. All caching key selects are returned from caching if the record exists there or the record is marked as nonexisting in the cache. An example of this kind of caching is found in the InventTxt table in the standard application. For this table it is a normal case that no record exists. Therefore, in this case it is of great value to register the nonexisting keys.
Important features about single-record caching are as follows:
2-18
•
The caching only works when selecting exactly one record, with a distinct where on the primary key.
•
The cache will not support fetch of multiple records in a query or a "while select".
•
The cache does not support data retrieved by joining more than one table.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data The cache on the client is not updated with changes done from other clients. This means that one client can cache and use invalid information. The single record caching is selected by the CacheLookup property setting on the table. You can use the AOT find tool to list some examples of this setting.
Entire Table Caching The entire table caching loads all the records in the table the first time one record is fetched. There is no limitation on the number of records cached. An individual cache is maintained for each data area in Microsoft Dynamics AX. The AOS holds the cache. This is shared between all clients connected to the AOS. The client supplements this AOS caching with a FoundAndEmpty single record caching if possible. The entire table caching is not used when making a join with other tables which is not entire table cached.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-19
Development III in Microsoft Dynamics® AX 2012 The entire table caching is selected by the CacheLookup property setting on the table. The individual users can disable the caching clicking Tools > Options > Preload. This form also provides an overview of which tables are configured by using entire table caching.
FIGURE 2.2 PRELOAD TABLES
Record View Caching Use record view caching to cache the records from a select statement (a result set). Result set caching is made available through the RecordViewCache class. The cache is instantiated using a select with the nofetch option. If the instantiating select is a join, a temporary table or is instantiated without the nofetch option, the cache is not instantiated and an error is reported. The select clause of the select defines the result set and only "==" predicates are accepted.
2-20
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data The following example illustrates the instantiating of a record view cache: SalesLine salesLine; RecordViewCache salesLineCache; ; select noFetch salesLine where salesLine.SalesId =="100"; salesLineCache = new recordViewCache(salesLine);
The cache is not shared between Microsoft Dynamics AX clients, and is deactivated as soon as the cache object goes out of scope or is destroyed. The cache is used when a select is from the same table, not a join and at least matches the where clause that the cache was instantiated with. If the table is not set up for Optimistic concurrency then using the forupdate option on the select within TTS results in database locks on all the rows that are put into the cache. A subsequent select forupdate within the same TTS is selected from the cache and not from the database as the row is already locked. Updates and deletes that match the instantiating where clause simultaneously maintain the database and the cache. Updates and deletes are not in the above category do not affect the result-set. The reread method retrieves data from the database and updates the copy data. The cache can be instantiated using a select without a where clause. This effectively creates a copy of the table in memory and functions like a personal table copy. The cache is useful for caching tables that are not of static nature, or that contain so many records that the other caching methods would be impractical. Because of concurrency issues, the forupdate keyword on the instantiating X++ select should only be used when all the records in the result set will be updated. Otherwise, it is a better strategy to select forupdate the records that are updated or use Optimistic concurrency on the tables. Do not use field-lists unless you are certain that the omitted fields will never be used. There is no warning or error when selecting fields from the cache that were not included in the select that instantiated the result set. Inserts normally do not affect the cache even when the record would match the where clause defining the result-set. When you use this form of caching, consider the memory consumption, as there is no internal limitation to the memory allocated for a single table. Different application behavior, with regard to exploiting caching when it is running on server versus client, could be an option.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-21
Development III in Microsoft Dynamics® AX 2012 One example of using record view caching is the class InventMovement. This holds information about inventory transactions related to one SalesLine, PurchLine and more. The cache is defined as a RecordViewCache object in \Classes\InventMovement\classDeclaration. The cache is initialized by the method \Data\Dictionary\Tables\InventTrans\Methods\viewCacheInventTransOrigin public static RecordViewCache viewCacheInventTransOrigin(InventTransOriginId _inventTransOriginId, boolean _forupdate = false) { InventTrans inventTrans; ; inventTrans.selectForUpdate(_forupdate); select nofetch inventTrans where inventTrans.InventTransOrigin == _inventTransOriginId; return new RecordViewCache(inventTrans); }
According to the cross reference, this method is called from \Classes\InventMovement\viewCacheInventTransId. void viewCacheInventTransOrigin() { if (viewCacheInventTrans) return; viewCacheInventTrans = null; viewCacheInventTrans = InventTrans::viewCacheInventTransOrigin(this.InventTransOri ginId(),true); }
Display Method Caching Caching display methods improves the performance of display functions if they are calculated on the AOS and improves performance when records are transferred from the server to the client. Only methods that are explicitly added to the cache are affected by the new caching mechanism. To sign up a method for caching, the method cacheAddMethod on the form data source should be called after super() in the init method of the data source.
2-22
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data The call to cacheAddMethod also determines how frequently the cached display method value is updated. The value is filled in upon fetching data from the backend, and it is refreshed when reread is called on the data source. Additionally, by default, the display method values are also updated when the record is written to the database. But that can be changed using the _updateOnWrite parameter in the cacheAddMethod. Only display methods that are of display type can be cached. Edit methods cannot be cached. Only display methods placed on the table, can be cached. One example of the initialization is found in \Forms\PurchTable\Data Sources\PurchLine\Methods\init. purchLine_ds.cacheAddMethod(tablemethodstr(PurchLine, receivedInTotal)); purchLine_ds.cacheAddMethod(tablemethodstr(PurchLine, invoicedInTotal)); purchLine_ds.cacheAddMethod(tablemethodstr(PurchLine, itemName)); purchLine_ds.cacheAddMethod(tablemethodstr(PurchLine, displayBudgetCheckResult)); purchLine_ds.cacheAddMethod(tablemethodstr(PurchLine, calcPendingQtyPurchDisplay));
Display methods not based on cached data, are candidates for display method caching.
Locking So far you have seen that the database server can access data faster when the number of requests to the database is minimized, by using efficient queries and caching. Another issue is if multiple users want to lock the same record at the same time. Only one user can lock the record, and the rest wait in a queue until the record is released. This has a substantial effect on the time used to perform a function. Locks are typically done in transactions where more than one record is locked at a time. The following example illustrates two different processes which lock different items: Process 1
Process 2
Item C - Locked
-
Item A - Locked
-
Item B - Locked
Item G - Locked
Item G - Waiting for lock from Process 2
Item A - Waiting for lock from Process 1
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-23
Development III in Microsoft Dynamics® AX 2012 This situation is called a dead lock; one of the transactions must be aborted to resolve the issue. Locking is the biggest issue with scalability, which means that many users are working with the same functions and data. To reduce locking, consider using Optimistic Concurrency Control (OCC). When the risk of overlapping transactions modifying the same data is small, optimistic locking can increase concurrency and thus transaction throughput. Enable OCC for a table by setting the property OccEnabled to Yes. NOTE: Only a few tables in the standard application do not use Optimistic Concurrency Control. Strategies to use to avoid or reduce the locking problem are as follows: •
Keep the transactions as short in time as possible. Do not compromise the integrity of the data.
•
Try to avoid locking central resources.
•
Try to lock central resources in the same order. This avoids the dead lock situation discussed previously. This serializes the process, which means that only one user can perform this at a time.
•
Never implement dialog with the user inside a transaction.
Locking is held for the time it takes to finish the process. If the process has poor performance because of missing optimization of AOS and database communication, the locking is a bigger issue and needs to be addressed first.
2-24
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data
Lab 2.3 - Reducing Locking Scenario Some users have been reporting performance issues and deadlock messages, when using some of your modifications. You have been given the task of optimizing your code, to reduce any locking.
Challenge Yourself! Modify the following job to reduce locking: InventTable inventTable; VendTable vendTable; ; ttsbegin; while select forupdate inventTable { if (VendTable::find(inventTable.PrimaryVendorId).Blocked == CustVendorBlocked::All) { inventTable.PrimaryVendorId =""; inventTable.update(); } } ttscommit;
Perform the following actions: 1. Create a new job with the above contents. 2. Try to reduce the locking. 3. Try to redesign the code to obtain a better performance.
Step by Step Solution 1: A new InventTable table variable is declared. Updates are done against this variable, so that you do not need to lock the whole InventTable forupdate. static void Locking_Solution_1(Args _args) { InventTable inventTable; InventTable inventTableUpdate; VendTable vendTable; ; ttsbegin; // solution #1
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-25
Development III in Microsoft Dynamics® AX 2012 while select inventTable { if (VendTable::find(InventTable.PrimaryVendorId).Blocked == CustVendorBlocked::All) { inventTableUpdate = InventTable::find(inventTable.ItemId, true); inventTableUpdate.PrimaryVendorId =""; inventTableUpdate.update(); } } ttscommit; }
Solution 2: If and find on VendTable are replaced with exist join on VendTable. static void Locking_Solution_2(Args _args) { InventTable inventTable; VendTable vendTable; ; ttsbegin; // solution #2 while select forupdate inventTable exists join vendTable where vendTable.AccountNum == inventTable.PrimaryVendorId && vendTable.Blocked == CustVendorBlocked::All { inventTable.PrimaryVendorId =""; inventTable.update(); } ttscommit; }
2-26
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data
Temporary Tables A temporary table is defined in the AOT as a normal table, but with the TableType property set to either InMemory or TempDB. Data is not persisted in a temporary table, and only remains while the table is in scope. If TableType is set to InMemory, the data is stored in memory or in a temporary file if memory is full. TempDb means it is stored in a table in SQL, and can be joined to regular tables at the database tier. A typical example is a class which initializes a temporary table and inserts some records, which should be shown in a form. The class has a variable _tmpTable which holds the data and the form has a data source tmpTable_DS which should show the same data. This is solved by using the method tmptable_DS.setTmpData(_tmpTable). This disregards the individual file allocated to tmptable_DS. Now, both _tmpTable and tmptable_DS will access the same pool of data, as if they were normal table variables. The allocated file now shared between _tmpTable and tmptable_DS is deleted once both variables have gone out of scope. Instead of making a dedicated definition of a temporary table in the AOT, consider making a temporary instance of a database table (in other words, a nontemporary table which is part of the SQL database). Do this using the .setTmp() method before accessing the variable. Be careful with this option, as you can activate other methods which act as if it is real database data and cause subsequent updates in other tables. See the following example, which copies all customers from Australia to a temporary table. static void CopyPersistedTableToTemp(Args _args) { CustTable custTable; CustTable tmpCustTable; ; tmpCustTable.setTmp(); while select custTable where custTable.CountryRegionId == "AU" { tmpCustTable.data(custTable.data()); tmpCustTable.doInsert(); } }
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-27
Development III in Microsoft Dynamics® AX 2012
Lab 2.4 - Temporary Tables Scenario As part of a larger modification, you need to implement some temporary tables, to achieve the required outcome.
Challenge Yourself! Make a job which works with two individual instances of temporary data (for example, TmpSum). Perform the following steps: 1. Make a job with two variables A and B of type TmpSum. 2. Insert some records in variable A. Does this have an effect on the contents of variable B? 3. Copy all records from A to B. Verify that the contents of the two tables are equal. 4. Delete one record from A. Does this have an effect on the contents of variable B? 5. Call the B.setTempData(A) method. Repeat step 2 and 4.
Step by Step If you declare two temporary table variables A and B, inserts, updates, and deletes on table A do not have impact on B. This only happens if the method variableB.setTempData(variableA) is set before updating. 1. Open the AOT create a new job. 2. Copy the following code in to the job. 3. Read through the code carefully, and try to understand what each line is doing and what the result will be. 4. Press F5 to run the job. Verify the results are what you expected. static void Temporary(Args _args) { TmpSum a; TmpSum b; Counter counter; void countRecords(Tmpsum _tmptable, str _step, str _name) { select count(RecId) from _tmptable; info(strFmt("Step %1 - %2 records in %3", _step, _tmptable.RecId, _name)); } ; ttsbegin;
2-28
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data // Insert records in 'a' for (counter = 1; counter Options > SQL. 10. Check SQL Trace, and then check Multiple SQL Statements > infolog. 11. Close the user options form. 12. In the AOT, right-click on view InventTableExpanded and select Synchronize.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-35
Development III in Microsoft Dynamics® AX 2012 13. An infolog box is displayed with a number of SQL commands. One of them is creating the view and is as follows:
FIGURE 2.4 CREATE VIEW
14. Note that one of the fields has the following : ,(CAST ((CASE WHEN T5.PRODUCTNAME IS NULL THEN T2.DISPLAYPRODUCTNUMBER ELSE T5.PRODUCTNAME END) AS NVARCHAR(60))) AS PRODUCTNAME,
Data Integration External data comes in many different formats. This section describes mechanisms to read and write data in four different, but commonly used, formats. With all external interaction in X++, code access permission must be asserted.
IO classes The standard classes that extend the IO class are useful for importing and exporting data to and from a simple text file format. Most commonly, these are TXT or CSV files.
2-36
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data The CommaIO and Comma7IO classes are used to import and export comma separated data. The TextIO and AsciiIO classes are used to import and export text-based data. The BinaryIO class is used to import and export binary data. The following code is an example of the CommaIO class: FileName fileName = 'c:\\test.csv'; FileIoPermission permission; CommaIO commaIO; container readCon; int i; int level; str column1; str column2; str column3; ; permission= new FileIoPermission(filename,'r'); permission.assert(); commaIO = new CommaIo(filename, 'r'); commaIO.inFieldDelimiter(';'); // Delimiter... if (commaIO) { while (commaIO.status() == IO_Status::OK) { readCon = commaIO.read(); column1 = conPeek(readCon, 1); column2 = conPeek(readCon, 2); column3 = conPeek(readCon, 3); info(strFmt("%1 - %2 - %3", column1, column2, column3)); } }
In this example, a specific field delimiter character has been specified. By default, the field delimiter character for CommaIO is the comma character, but in this case the code specifies the semi-colon character as the field delimiter. The commaIO object is looped over, using a "while" statement, which continues until the IO_status is no longer OK, which indicates the end of the file. The CommaIO.read() method returns data from a single record (or row) from the file, in the form of a container, where each container element is a single field value. In this example, there are at least three fields per row.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-37
Development III in Microsoft Dynamics® AX 2012 FileIO FileIO is commonly used to export data to a TXT file. There are three different methods to write text to an FileIO object: •
FileIO.write(str) - writes a string.
•
FileIO.writeExp(container) - writes the contents of a container. Elements are separated by character defined in FileIO.outFieldDelimiter().
•
FileIO.writeChar(int) - writes a unicode character represented by an integer.
The following is sample code which writes a simple string to a text file: FileName fileName = 'c:\\test.txt'; FileIoPermission permission; FileIO fileIO; str outputText; #File ; permission= new FileIoPermission(filename,#io_write); permission.assert(); fileIO= new FileIO(filename, #io_write); if (fileIO) { outputText = "text that will go into the text file."; fileIO.write(outputText); //write the text to the file. fileIO.finalize(); //finish the file. }
Creating XML Extensible Markup Language (XML) is a text-based standard for representing data and is commonly used for data exchange and business-to-business communication. The structure of XML files is similar to HTML in that it uses tags to define sections of the file. Each tag defines either single or groups of data elements, and can also have attributes (for example, a date or reference ID) attached to the tag. The following is an example of a simple XML file: Microsoft Dynamics AX 2012 This is a simple XML file
2-38
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data Since it is a simple text file, it is possible to create an XML file using Microsoft Dynamics AX AsciiIO class, and to ensure that all the tags are correctly formatted and positioned. Be careful though as the tags are case-sensitive. Alternatively, Microsoft Dynamics AX includes a wrapper for the COM object microsoft.xmldom, which creates the file using tag names and data. The following example creates an XML file containing employee names and addresses: static void XMLWriteEmplAddr(Args _args) { FileIoPermission permission; XMLDocument xmlDoc = XMLDocument::newBlank(); XMLNode rootNode; XMLNode NodeEmpl, NodeName, NodeAddr; XMLElement xmlElement; XMLText xmlText; HCMWorker HCMWorker; ; permission= new FileIoPermission('c:\\Empl_Address_List.xml','w'); permission.assert(); xmlDoc
= XMLDocument::newBlank();
// Create first line containing version info rootNode = xmlDoc.documentElement(); xmlElement = xmlDoc.createElement(‘EmployeeList’); rootNode = xmlDoc.appendChild(xmlElement); while select EmplTable { // Create a node for the Employee record xmlElement = xmlDoc.createElement(‘Employee’); NodeEmpl = rootNode.appendChild(xmlElement); // Create a node for the name xmlElement = xmlDoc.createElement(‘EmplName’); NodeName = NodeEmpl.appendChild(xmlElement); xmlText = xmlDoc.createTextNode(EmplTable.Name()); NodeName.appendChild(xmlText); // Create a node for the address xmlElement = xmlDoc.createElement(‘EmplAddr’); NodeAddr = NodeEmpl.appendChild(xmlElement); xmlText = xmlDoc.createTextNode(EmplTable.Address); NodeAddr.appendChild(xmlText); }
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-39
Development III in Microsoft Dynamics® AX 2012
}
// Save the file xmldoc.save('c:\\Empl_Address_List.xml');
Reading XML XML files can be read in an easy way or a hard way. The hard way is to read the file line by line and determine the correct tag each time. The easy way is to let the microsoft.xmldom COM object do the work. The following example reads an XML file containing customer account number and transaction details. static void XMLReadCustTrans(Args _args) { FileIoPermission permission; XMLDocument doc; XMLNode rootNode, AccNode, NameNode, transNode; XMLNodeList custTransList; XMLParseError xmlError; int i; ; permission= new FileIoPermission("C:\\CustTrans.xml",'r'); permission.assert(); // Get the XML document doc = new XMLDocument(); doc.load("C:\\CustTrans.xml"); xmlError = doc.parseError(); if (xmlError && xmlError.errorCode() != 0) throw error(strFmt("Error: %1",xmlError.reason())); rootNode = doc.documentElement(); // Get the customer account and name from the document AccNode = rootNode.selectSingleNode("//CustomerAccount"); NameNode = rootNode.selectSingleNode("//CustomerName"); setprefix(AccNode.text()+ ' ' + NameNode.text()); // Select all the customer transactions into a nodelist custTransList = rootNode.selectNodes("//TransactionDetail"); for (i = 0; i < custTransList.length(); i++) { transNode = custTransList.item(i);
2-40
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data info(transNode.selectSingleNode("TransText").text() + ' ' + transNode.selectSingleNode("TransAmount").text()); } }
ODBC Sometimes, data that you need to access from Microsoft Dynamics AX may be stored in an alternative database. This is common when fetching data from an external application, either on a regular basis, or during data conversion. An Open DataBase Connection (ODBC) is a simple way for Microsoft Dynamics AX to access an external database, and perform queries on it to fetch the required data. For this to work, an ODBC DataSource Name (DSN) needs to be created in Windows. A DSN acts as a thin database client, usually including all authentication information like username and password. The DSN needs to be created on the tier where the X++ code will call it from (in other words, either the client or the AOS). Best practice is to keep the DSN on the AOS, to help scalability. Once a DSN is available in Windows, code can be written to leverage its access to the external database: static void TestODBC() { LoginProperty loginProperty; OdbcConnection odbcConnection; Statement statement; ResultSet resultSet; str sql, criteria; SqlStatementExecutePermission perm; ; //Set information on the ODBC loginProperty = new LoginProperty(); loginProperty.setDSN("dsn"); loginProperty.setDatabase("databaseName"); //Create connection to external DB odbcConnection = new OdbcConnection(loginProperty); if (odbcConnection) { sql = "SELECT * FROM MYTABLE WHERE FIELD =" + criteria + " ORDER BY FIELD1,FIELD2 ASC" //assert permission for sql string perm = new SqlStatementExecutePermission(sql);
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-41
Development III in Microsoft Dynamics® AX 2012 perm.assert();
//Prepare statement statement = odbcConnection.createStatement(); resultSet = statement.executeQuery(sql); //Running statement while (resultSet.next()) { //It is not possible to get field 3 and then 1. Always get fields in numerical order: 1,2,3,4,5,6 print resultSet.getString(1); print resultSet.getString(3); } //Shutting down the connection resultSet.close(); statement.close();
} else error("Failed to log on to the database"); }
The previous code uses a variable called "criteria", and includes it in the SQL string that is executed against the example external database. It then prints the string values from columns 1 and 3 in the result set. Columns in the result set can only be called in numerical order. If you need to print column 3 value first, first fetch the column 1 value and store it in a local variable, then fetch and print the column 3 value, then print the local variable. More methods are available on the ResultSet object, to fetch other types from the record. These include, getReal(), getDate(), getInt(). The string that is passed to the statement.executeQuery() method must be an SQL query in the format that the external database can understand. For example, if you are querying an Oracle database, the string must contain a valid Oracle SQL query.
Microsoft Excel Microsoft Dynamics AX includes built-in administration tools for importing and exporting table data using the Microsoft® Excel® spreadsheet format. However, sometimes it may be necessary to perform similar Microsoft Excel data import and export functions from a custom function. Often, this can be done using Comma Separated Value (CSV) files and the CommaIO class, but sometimes using XLS or XLSX formatted files is a requirement. To make this possible, there are various classes to perform Microsoft Excel integration. These all start with the prefix SysExcel and can be found in the Class branch of the AOT.
2-42
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data The following is an example of some X++ code that integrates with an XLS file, and pulls a value from a specific cell. SysExcelApplication ExcelApp; Str cellValue; ; //Creates the instance ExcelApp = SysExcelApplication::construct(); //Opens the file ExcelApp.workbooks().open('c:\test.xls'); //Shows the value of the selected cell. cellValue = ExcelApp.activeSheet().cells().item(1, 1).value().toString(); //both columns and rows are referenced numerically, eg. A=1, B=2 etc info(strfmt("Value of cell A1 = %1", cellValue));
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-43
Development III in Microsoft Dynamics® AX 2012
Lab 2.5 - Integrating External Data Scenario Your company needs to import seasonal sales prices every quarter. The sales prices are maintained in a spreadsheet that need to be imported in to AX once they are complete. You have been asked to write a program to import the sales prices.
Challenge Yourself! To complete this lab, you need to create an external data file. Create a new file with the following structure: Item number, Date, Amount For example your file could look like this:
FIGURE 2.5 SALES PRICE IMPORT FILE
Save the file as a CSV file named SalesPrice.csv in the root C drive directory. Write a job in X++ that will import the data from the SalesPrice.csv file that you just created. The job will need to set some values for other fields - the price should be for All customer accounts, use the current companies default currency, be applicable for all quantities and for any inventory dimension.
Step by Step 1. Open the AOT and create a new Job. 2. Copy the following code to the job. static void ImportSalesPrice(Args _args) { CommaIO commaIO; container readCon; str column1, column2, column3; PriceDiscTable priceDiscTable; #File ;
2-44
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data commaIO = new CommaIO('c:\\salesprice.csv', #io_read); if (commaIO) { while (commaIO.status() == IO_Status::OK) { readCon = commaIO.read(); column1 = conPeek(readCon, 1); column2 = conPeek(readCon, 2); column3 = conPeek(readCon, 3); priceDiscTable.relation = PriceType::PriceSales; priceDiscTable.AccountCode = TableGroupAll::All; priceDiscTable.Currency = Ledger::accountingCurrency(); priceDiscTable.ItemRelation = column1; priceDiscTable.FromDate = str2date(column2,123); priceDiscTable.Amount = str2num(column3); priceDiscTable.InventDimId = InventDim::findOrCreateBlank().inventDimId; priceDiscTable.QuantityAmountFrom = 1; pricedisctable.insert(); } } }
3. Press F5 to run the job. 4. You can check that the prices have been imported by navigating to Product information management > Released products. Find the items that you have set the price for in the file, and then click Sell > Sales price.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-45
Development III in Microsoft Dynamics® AX 2012
Summary Using the most efficient approaches to database communication will result in an application that runs with the best performance possible. Using the tools described in this chapter, modifications made to the standard application can operate at the peak performance expected by the users.
2-46
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data
Test Your Knowledge Test your knowledge with the following questions. 1. Match the select statement clauses, with their correct purpose: _____ 1. Fieldlist _____ 2. FirstOnly _____ 3. FirstFast _____ 4. ForceNestedLoop _____ 5. ForceSelectOrder _____ 6. CrossCompany _____ 7. Index
a. This keyword forces the database to use a specific index as the order by clause. b. This keyword forces the database to prioritize fetching the first few rows fast over fetching the complete result set. c. This keyword forces the database to return records regardless of dataAreaId, or for a set of specific dataAreaId's. d. This keyword forces the database server to use a nested-loop algorithm to process a given SQL statement that contains a join. e. Used to specify fields which will be fetched from the database. f. This keyword forces the database server to access the tables in a join in the given order. g. This keyword forces the database to return only the first found record.
2. You have the following X++ code, with two QueryBuildDatasource objects. Modify the second line and add a third line, so that these two datasources are joined: qbds1 = query.addDatasource(tableNum(CustTable)); qbds2 = query.addDatasource(tableNum(CustTrans));
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-47
Development III in Microsoft Dynamics® AX 2012 3. Which of the following are true for avoiding locking: (Select all that apply) ( ) Keep the transactions as short in time as possible. ( ) Try to lock central resources. ( ) Try to lock central resources in the same order. ( ) Try to implement dialogs with the user inside transactions. 4. A temporary table is declared in an object running on the client. The first record is inserted from a method running on the AOS. Where is the data in the temporary table physically stored?
5. If you create a new field on CustTable and SalesTable, where is it most appropriate to set the field on SalesTable from the field on CustTable?
2-48
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data 6. Why does Microsoft Dynamics AX use Parm tables?
7. Which line of code adds a new node into an XML file: ( ) Node = rootNode.AddChild(xmlDoc.createElement("Customer")); ( ) Node = rootNode.AppendChild(xmlDoc.createElement("Customer")); ( ) Node = AddChild(rootNode).(xmlDoc.createElement("Customer")); ( ) Node = rootNode.(xmlDoc.create("Customer"). appendChild()); 8. Identify the problem with the following block of ODBC code, where the resultSet has already been successfully fetched from the external database: while (resultSet.next()) { print resultSet.getString(2); print resultSet.getString(5); print resultSet.getReal(4); print resultSet.getInt(7); }
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-49
Development III in Microsoft Dynamics® AX 2012
Quick Interaction: Lessons Learned Take a moment and write down three key points you have learned from this chapter 1.
2.
3.
2-50
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 2: Working with Data
Solutions Test Your Knowledge 1. Match the select statement clauses, with their correct purpose: e 1. Fieldlist g 2. FirstOnly b 3. FirstFast d 4. ForceNestedLoop f 5. ForceSelectOrder c 6. CrossCompany a 7. Index
a. This keyword forces the database to use a specific index as the order by clause. b. This keyword forces the database to prioritize fetching the first few rows fast over fetching the complete result set. c. This keyword forces the database to return records regardless of dataAreaId, or for a set of specific dataAreaId's. d. This keyword forces the database server to use a nested-loop algorithm to process a given SQL statement that contains a join. e. Used to specify fields which will be fetched from the database. f. This keyword forces the database server to access the tables in a join in the given order. g. This keyword forces the database to return only the first found record.
2. You have the following X++ code, with two QueryBuildDatasource objects. Modify the second line and add a third line, so that these two datasources are joined: qbds1 = query.addDatasource(tableNum(CustTable)); qbds2 = query.addDatasource(tableNum(CustTrans)); MODEL ANSWER: qbds2 = qbds1.addDatasource(tableNum(CustTrans)); qbds2.relations(TRUE); 3. Which of the following are true for avoiding locking: (Select all that apply) (√) Keep the transactions as short in time as possible. ( ) Try to lock central resources. (√) Try to lock central resources in the same order. ( ) Try to implement dialogs with the user inside transactions.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
2-51
Development III in Microsoft Dynamics® AX 2012 4. A temporary table is declared in an object running on the client. The first record is inserted from a method running on the AOS. Where is the data in the temporary table physically stored? MODEL ANSWER: The data in the temporary table are stored in a file on the AOS because the first record is inserted from a method running on the AOS. 5. If you create a new field on CustTable and SalesTable, where is it most appropriate to set the field on SalesTable from the field on CustTable? MODEL ANSWER: SalesTable.InitFromCustTable() 6. Why does Microsoft Dynamics AX use Parm tables? MODEL ANSWER: Microsoft Dynamics AX uses Parm tables to store data that will be updated on sales orders, purchase orders. This allows the user to view and modify what will be updated without affecting the original order. It also enables the update to be processed by the batch system. 7. Which line of code adds a new node into an XML file: ( ) Node = rootNode.AddChild(xmlDoc.createElement("Customer")); (•) Node = rootNode.AppendChild(xmlDoc.createElement("Customer")); ( ) Node = AddChild(rootNode).(xmlDoc.createElement("Customer")); ( ) Node = rootNode.(xmlDoc.create("Customer"). appendChild()); 8. Identify the problem with the following block of ODBC code, where the resultSet has already been successfully fetched from the external database: while (resultSet.next()) { print resultSet.getString(2); print resultSet.getString(5); print resultSet.getReal(4); print resultSet.getInt(7); } MODEL ANSWER: All columns in the result set need to be called in numerical order. Column 4 cannot be called after column 5.
2-52
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 3: Classes
CHAPTER 3: CLASSES Objectives The objectives are: •
Use collection classes to store data in X++.
•
List which application objects control different Graphical User Interface (GUI) components.
•
Modify and use the Application Substituted Kernel Classes.
•
Extend the RunBase framework to create new batch processes.
•
Transfer information using the Args object.
Introduction Microsoft Dynamics® AX provides a large range of standard system classes that you can use while developing X++ code. This lesson introduces some of the most commonly used system classes, and demonstrates ways they can be used to support modifications.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
3-1
Development III in Microsoft Dynamics® AX 2012
Collection Classes X++ contains two compound data types: arrays and containers. They are useful for aggregating values of simpler data types. However, you cannot store objects in arrays or containers. The Microsoft Dynamics AX collection classes have been designed for storing objects. The following collection classes are available: •
List
•
Map
•
Set
•
Array
•
Struct
The following classes also aide in aggregating data: •
RecordSortedList
•
RecordInsertList
List A List object contains members that are accessed sequentially. Lists are structures that can contain members of any X++ type. All the members in the same list must be of the same type. The following methods are commonly used on List objects: •
addEnd(anytype) - adds a member to the end of the list.
•
addStart(anytype) - adds a member to the start of the list.
•
elements() - returns the number of members contained in the list.
•
getEnumerator() - returns a ListEnumerator object for this List object.
•
typeId() - returns the Type that the List contains.
The ListEnumerator class allows you to traverse through the elements within a list. The following methods are commonly used on ListEnumerator objects:
3-2
•
current() - retrieves the value of the item currently pointed to in the list.
•
moveNext() - moves the enumerator to the next item in the list. List enumerators start before the first element in the list, so moveNext() must be called to make it point to the first element in the list.
•
reset() - moves the enumerator to the start of the list.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 3: Classes The following example demonstrates how to create a list, then move through it and extract values from it. 1. Create a new List object, specifying the data type it will contain. 2. Add members to the List object, using the List.addEnd() and List.addStart() methods. 3. Set the ListEnumerator object using the List.getEnumerator() method. 4. Go to the start of the list, using ListEnumerator.reset(). 5. Move through the members of the list, using ListEnumerator.moveNext(). 6. Pull the value of the current member in the list, using ListEnumerator.current(). List integerList = new List(Types::Integer); ListEnumerator enumerator; // Add some elements to the list integerList.addEnd(1); integerList.addEnd(2); integerList.addStart(3); // Set the enumerator enumerator = integerList.getEnumerator(); // Go to beginning of enumerator enumerator.reset(); //Go to the first element in the List enumerator.moveNext(); // Print contents of first and second elements info(strfmt("First element is %1", enumerator.current())); enumerator.moveNext(); info(strfmt("Second element is %1", enumerator.current()));
When the code is executed, the result in the infolog should be as follows: •
First element is 3
•
Second element is 1
Map A Map object associates one value (the key) with another value. Both the key and value can be of any valid X++ type, including objects. The types of the key and value are specified in the declaration of the map. The way in which maps are implemented means that access to the values is very fast.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
3-3
Development III in Microsoft Dynamics® AX 2012 Multiple keys can map to the same value, but one key can map to only one value at a time. If a [key, value] pair is added where the key already exists, it will replace the existing pair with that key value. The following methods are commonly used on Map objects: •
insert(_key, _value) - inserts a member (pair) into the map, where _key and _value can be anytype.
•
remove(_key) - removes a [key, value] pair from a map.
•
exists(_key) - determines whether a particular value exists as a key in the map.
•
lookup(_key) - returns the value mapped to by a particular key value.
•
elements() - returns the number of members contained in the map.
•
getEnumerator() - returns a MapEnumerator object for this Map object.
The MapEnumerator class allows you to traverse through the members of a map. The following methods are commonly used on MapEnumerator objects: •
currentKey() - retrieves the key of the pair currently pointed to in the map.
•
currentValue() - retrieves the value of the pair currently pointed to in the map.
•
moveNext() - moves the enumerator to the next pair in the map. Map enumerators start before the first pair in the map, so moveNext() must be called to make it point to the first pair in the map.
•
reset() - moves the enumerator to the start of the map.
The following example demonstrates how to create a map of Customers per State, then move through it and extract values from it. 1. Create a new Map object, specifying the data type it will contain. 2. Loop through the records in the CustTable. For each, use Map.exists() to check if CustTable.stateName() already exists as a key in the map. 3. If it does not already exist, use Map.insert() to insert a pair of (CustTable.stateName(), 1). 4. If it does already exist, use Map.insert() to re-insert the pair with the same key (which will overwrite the existing pair), but the new value equals the existing pair value plus 1. 5. Set the MapEnumerator object using the Map.getEnumerator() method. 6. Go to the start of the map, using MapEnumerator.reset().
3-4
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 3: Classes 7. Move through the members of the map, using MapEnumerator.moveNext(). 8. Pull the value of the current member in the map, using MapEnumerator.currentKey() and MapEnumerator.currentValue(). Map mapStateNumbers; MapEnumerator enumerator; CustTable custTable; mapStateNumbers = new Map(Types::String, Types::Integer); while select custTable { if(mapStateNumbers.exists(custTable.stateName())) { mapStateNumbers.insert(custTable.stateName(), mapStateNumbers.lookup(custTable.stateName())+1); } else { mapStateNumbers.insert(custTable.StateName(), 1); } } enumerator = mapStateNumbers.getEnumerator(); while (enumerator.moveNext()) { info(strfmt("%1 customers are located in %2.", enumerator.currentValue(), enumerator.currentKey()); }
Set A Set is used for the storage and retrieval of data from a collection in which the members are unique. The values of the members serve as the key according to which the data is automatically ordered. Thus, it differs from a List collection class where the members are placed into a specific position, and not ordered automatically by their value. Set members may be of any X++ type. All members in the set must have the same type. When a value that is already stored in the set is added again, it is ignored and does not increase the number of members in the set. The following methods are commonly used on Set objects: •
add(anytype) - inserts a value into the set.
•
remove(anytype) - removes a value from the set.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
3-5
Development III in Microsoft Dynamics® AX 2012 •
in(anytype) - determines whether a particular value is a member of the set.
•
elements() - returns the number of members contained in the set.
•
getEnumerator() - returns a SetEnumerator object for this Set object.
The SetEnumerator class allows you to traverse through the members within a set. The following methods are commonly used on SetEnumerator objects: •
current() - retrieves the value currently pointed to in the set.
•
moveNext() - moves the enumerator to the next value in the set. Set enumerators start before the first value in the set, so moveNext() must be called to make it point to the first value in the set.
•
reset() - moves the enumerator to the start of the set.
The following example demonstrates how to create two sets, and then compares each set to detect any shared members and remove them. 1. Create two new Set objects, specifying the data type they will contain. 2. Add members to the Set objects, using the Set.insert() method. 3. Set a SetEnumerator object for one of the Sets, using the Set.getEnumerator() method. 4. Go to the start of the set, using SetEnumerator.reset(). 5. Move through the members in the set, using SetEnumerator.moveNext(). 6. Pull the value of the current member in the set, using SetEnumerator.current(). 7. Search for the value in the second set, using Set.in(). If found, remove it using Set.remove(). Set Set SetEnumerator Int
setOne; setTwo; enumerator; value;
setOne = new Set(types::Integer); setOne.add(1); setOne.add(2); setOne.add(3); setTwo = new Set(Types::Integer); setTwo.add(3); setTwo.add(4); setTwo.add(5); enumerator = setOne.getEnumerator();
3-6
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 3: Classes while (enumerator.moveNext()) { value = enumerator.current();
}
if (setTwo.in(value)) { setTwo.remove(value); }
Array Array objects may hold values of any single type, including objects and records. Objects of this type may be transferred to functions and methods. The values are stored sequentially. The following methods are commonly used on Array objects: •
exists(int) - checks if a value exists in a specific position in the array.
•
lastIndex() - retrieves the highest index that is used to store a value in the array.
•
value(int _index, [anytype _value]) - gets or sets the value of the array member that is stored at the specified index.
•
typeId() - returns the data type that the Array contains.
The following is an example using an Array object that holds three different Query objects: Array array = new Array (Types::Class); array.value(1, new Query()); array.value(2, new Query()); array.value(3, new Query());
Struct Struct objects (short for structures) are entities that can hold a number of values of any X++ type. Structs are used to group information about a specific entity. For example, there may be a need to store information about a customer's name, group and address and then treat this compound information as one item. The following methods are commonly used on struct objects: •
add(str _fieldName, anytype _value) - adds a new field to the struct and assigns the specified value to it.
•
exists(str _fieldName) - determines whether a particular field exists in a struct.
•
fieldName(int _index) - returns the name of the field in the struct at the position specified.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
3-7
Development III in Microsoft Dynamics® AX 2012 •
fields() - returns the number of fields in the struct.
•
index(str _fieldName) - calculates the position of a field in the struct based on its name.
•
remove(str _fieldName) - removes a field from a struct.
•
value(str _fieldName, anytype _value) - gets or sets the value for a specific field.
•
valueIndex(int _index, anytype _value) - gets or sets the value of the field at a specific index position in a struct.
The fields in a struct are specified by a data type and a name. Fields can be added when the struct is created by using the new() method, or they can be created after the struct has been created using the add() method. If a field is added using the add() method, the value is specified for the field at the same time, and the data type is inferred from this value. If a field is added during the instantiation of the struct, the value() or the valueIndex() method needs to be used to assign a value to it. The fields in a struct are arranged in alphabetical order of the field names. The following is an example using the Struct object. Struct struct = new Struct(); int i; // Add new fields and values struct.add("Group", "DOM"); struct.add("Name", "Jane Doe"); struct.add("Shoesize", 45); // Prints the type and name of all items in the struct for (i = 1 ; i = fromDate custTrans.TransDate = fromDate && custInvoiceTrans.InvoiceDate New > Project. 3. Select Visual C# and then Windows from the Installed Templates list. 4. Select Class Library template. 5. Enter a project name into the Name field and then click OK. 6. Verify the project is created and is visible in the Solution Explorer. 7. Save the project. 8. Click Build and then Build Solution. 9. Right-click on the project in the Solution Explorer and select Add to AOT. Notice the project icon now has the Microsoft Dynamics icon incorporated, indicating this project is now connected with the AOT. 10. Open the Application Explorer in Visual Studio. 11. In the Classes node, find the SalesTableType class. 12. Drag the SalesTableType class onto the project in the Solution Explorer. The class should now appear as a proxy in the projects contents.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
5-9
Development III in Microsoft Dynamics® AX 2012
Lab 5.2 - Create an Event Handler in Managed Code You have been asked to write an event handler for a Microsoft Dynamics AX event, using managed C# code in Visual Studio. Scenario The event that you have been asked to handle, is the ValidateWrite method on the SalesTableType (called to validate a SalesTable record before it is written). You have been asked to create a post-event handler using managed code. You will also need to create a managed code project to hold this event handler (the project created in Lab 5.1 can be used for this purpose).
Challenge Yourself! 1. Use the Application Explorer to find the method that needs to be handled. 2. Create a post-event handler. 3. Ensure the event handler will be deployed on the Server and Client.
Step by Step 1. Open Visual Studio. 2. If you are reusing the project from Lab 5.1, skip to step 4. Otherwise, create a Managed Code project. 3. Right-click on the project in the Solution Explorer and select Add to AOT. 4. If the Application Explorer is not visible, open it. 5. In the code editor, open the class and place the cursor somewhere inside the class. 6. In the Application Explorer, locate the SalesTableType class. Expand it and locate the validateWrite method. 7. Right click the validateWrite method and select Add post-event handler. 8. Notice that an event handler method is created in the class.
5-10
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 5: Visual Studio Integration
Summary By using the integrated Microsoft Dynamics AX tool set inside Visual Studio, developers can now write managed code in any .NET language to enhance or modify Microsoft Dynamics AX business logic. The Application Explorer tool offers a development environment similar to that within Microsoft Dynamics AX, making switching between the two easier and more familiar.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
5-11
Development III in Microsoft Dynamics® AX 2012
Test Your Knowledge Test your knowledge with the following questions. 1. What type of Visual Studio projects can be used to integrate with Microsoft Dynamics AX?
2. What key step in creating a managed code project will connect the project with the AX tool set within Visual Studio?
3. What four deployment properties are available for a Visual Studio project that has been added to the Microsoft Dynamics AX AOT?
5-12
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 5: Visual Studio Integration 4. What needs to happen before the Visual Studio debugger will activate on breakpoints inside managed code called from the Microsoft Dynamics AX client or server?
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
5-13
Development III in Microsoft Dynamics® AX 2012
Quick Interaction: Lessons Learned Take a moment and write down three key points you have learned from this chapter 1.
2.
3.
5-14
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 5: Visual Studio Integration
Solutions Test Your Knowledge 1. What type of Visual Studio projects can be used to integrate with Microsoft Dynamics AX? MODEL ANSWER: Report modeling projects, Enterprise Portal Web application projects, and Managed code projects. 2. What key step in creating a managed code project will connect the project with the AX tool set within Visual Studio? MODEL ANSWER: Right-clicking the project and selecting Add to AOT. 3. What four deployment properties are available for a Visual Studio project that has been added to the Microsoft Dynamics AX AOT? MODEL ANSWER: Deploy to Server, Deploy to Client, Deploy to EP, Deploy to SSRS. 4. What needs to happen before the Visual Studio debugger will activate on breakpoints inside managed code called from the Microsoft Dynamics AX client or server? MODEL ANSWER: The Visual Studio Debugger needs to be attached to either the Server or Client process for Microsoft Dynamics AX.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
5-15
Development III in Microsoft Dynamics® AX 2012
5-16
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow
CHAPTER 6: WORKFLOW Objectives The objectives are: •
Configure how the workflow engine is executed on a server.
•
Specify which application module a workflow is applicable to using a workflow category.
•
Link tables to workflows using a query.
•
Create a new workflow type.
•
Apply a workflow to a form.
•
Define what happens when the workflow is approved or denied.
•
Create Event Handlers and apply them to a workflow.
•
Configure a workflow.
Introduction Workflow is a system in Microsoft Dynamics® AX 2012 that allows business processes to be automated. For example, a purchase requisition may need to be approved by a number of different employees according to the requisition's total amount. Each employee has to approve it before the next employee in the approval route. A Workflow in Microsoft Dynamics AX uses a combination of Application Object Tree (AOT) elements created by IT and additional set up that a user can control. This course introduces the development side of creating a workflow. To create a workflow you will need to use skills developed from this class and the Morph X development class.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-1
Development III in Microsoft Dynamics® AX 2012 Scenario Isaac, the systems developer, has been asked to create a new workflow that will be used to approve a new sales order for a customer that has reached their credit limit. The requirement is that when a new sales order is entered which takes the customer over their credit limit, the sales order is submitted to the Accounts Receivable (AR) manager. They will either approve or deny the sales order. Until it is approved, the sales order cannot be picked, packed or invoiced.
Workflow Configuration There are three batch jobs that manage workflow processing. These jobs are all run on the Application Object Server (AOS) using the Batch system. To set this up, run System Administration > Setup > Workflow > Workflow infrastructure configuration. Enter batch groups for each process. Batch groups can be used to control which AOS instance each workflow batch job is run on.
Create a Workflow Category A workflow category is used to determine the module in which the workflow will be available. Modules are defined by the ModuleAxapta enum.
Demonstration: Creating a Workflow Category Perform the following steps to create a category that allows the workflow to be authored from the Sales and Marketing module. 1. Open the AOT. 2. Expand the Workflow node. 3. Right-click on the Workflow Categories node and select New Workflow Category. A new workflow category called Workflow Category1 will be created. 4. Right-click on the newly created workflow category and select Properties. 5. Change the name property to SalesCategory. 6. Change the label property to Sales workflows.
6-2
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow 7. Change the Module property to SalesOrder. 8. Right-click on the newly created workflow category and select Save.
FIGURE 6.1 WORKFLOW TYPE WIZARD
Create a Query A workflow uses a query to define the data that is used by the workflow. It can define one or more tables and all or selected fields on that table.
Demonstration: Creating a Query A query defines what tables are used to determine that a workflow can be initiated. Use a class to bind that query to the workflow type. Perform the following steps to create a query. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12.
Open the AOT. Right-click on the Queries node and select New Query. Rename the query to SalesCreditLimitApproval. Expand the newly created query. Open another AOT window. Expand Data Dictionary > Tables. Find the table SalesTable. Drag the SalesTable table to the Data Sources node of the SalesCreditLimitApproval query. Expand the SalesTable_1 node Right-click on the Fields node and select Properties. Set the Dynamics property to Yes and close the property sheet. Right click on the SalesCreditLimitApproval query and select Save.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-3
Development III in Microsoft Dynamics® AX 2012
Create a Workflow Type A workflow type defines the business document being acted upon, the supported AOT elements, and event handlers and menu items for the workflow. Workflows are created based on a type, and many workflows can be based on the same type.
Demonstration: Creating a Workflow Type Perform the following steps to create a workflow type and bind it to the workflow category created in the previous demonstration. 1. Open the AOT. 2. Expand the Workflow node. 3. Right-click on the Workflow Types node and select Add-ins > Workflow type wizard. 4. Click Next. 5. Enter SalesCreditLimitAppr in the name. 6. Enter SalesCategory in the Category. 7. Enter SalesCreditLimitApproval in the query. 8. Enter SalesTable in the Document menu item.
FIGURE 6.2 WORKFLOW TYPE
9. Click Next. 10. Click Finish. A development project with a number of newly created elements will be displayed.
6-4
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow
Enable Workflow on a Form Now that both the workflow type and approval element are defined, you can specify which forms will use it.
Demonstration: Add a WorkflowState Field Any form that uses the same table in the datasource as the query referenced in a workflow document can be enabled for a workflow. This demonstration shows how to enable workflow on the SalesTableListPage form. You can specify conditions under which a workflow is eligible for submission. One of these conditions must be that it has not already been submitted. To test this condition, use a new field on the SalesTable table. Perform the following steps to add a workflow state field. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
Open the AOT. Expand Data Dictionary. Right-click on Base Enums and select New Base Enum. Rename the new enum to SalesCreditLimitApprovalStatus Add four elements to the Enum called NotSubmitted, Submitted, Approved, Rejected. Expand Tables > SalesTable. Right-click on Fields and select New > Enum. Right-click on the newly created field and select Properties. Change the Name property to CreditLimitApprovalStatus. Change the EnumType property to SalesCreditLimitApprovalStatus. Right-click on the SalesTable node and select Save.
Demonstration: Enable Workflow on the Form Workflow on the form is enabled using properties on the design node, and by overriding a form method. Perform the following steps to enable workflow on a form. 1. 2. 3. 4. 5. 6. 7.
Open the AOT. Expand Tables > SalesTable. Create a new method and add the method in the following code. Save the changes made to the table. Expand Forms > SalesTableListPage > Designs. Right-click on the design node and select Properties. Change the WorkflowEnabled property to Yes.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-5
Development III in Microsoft Dynamics® AX 2012 8. Change the WorkflowDatasource property to SalesTable. 9. Change the WorkflowType property to SalesCreditLimitAppr 10. Save your changes to the form. boolean canSubmitToWorkflow(str _workflowType = '') { amountMST creditBalance; custTable custTable; ; if (!this.CreditLimitApprovalStatus == SalesCreditLimitApprovalStatus::NotSubmitted) return false; custTable = this.custTable_InvoiceAccount(); if (!custTable.CreditMax) return false; creditBalance = custTable.CreditMax custTable.balanceMST(); if (this.amountRemainSalesFinancial() + this.amountRemainSalesPhysical() < creditBalance) return false; return true; }
Demonstration: Create a Submit to Workflow Class To submit a document for workflow processing, call standard code to prompt the user for a comment and to process the submission. Perform the following steps to create a submit to workflow class. 1. Press Ctrl+Shift+P to open the development projects window. 2. Expand Private. 3. Double-click on SalesCreditLimitApprWFType developement project. 4. In the development project find the class SalesCreditLimitApprSubmitManager. 5. Create a new method called submit, and copy the following code for this method. 6. Modify the code for the main method as shown in the following code. 7. Press F8 to save and compile the code. 8. Find the menu item SalesCreditLimitApprSubmitMenuItem.
6-6
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow 9. Change the Label property to Submit. 10. Right-click on the SalesCreditLimitApprSubmitMenuItem menu item and select Save. void submit(Args args) { // Variable declaration. recId recId = args.record().RecId; WorkflowCorrelationId workflowCorrelationId; // Hardcoded type name WorkflowTypeName workflowTypeName = workflowtypestr(SalesCreditLimitAppr); // Initial note is the information that users enter when they // submit the document for workflow. WorkflowComment note =""; WorkflowSubmitDialog workflowSubmitDialog; SalesTable SalesTable; // Opens the submit to workflow dialog. workflowSubmitDialog = WorkflowSubmitDialog::construct(args.caller().getActiveWork flowConfiguration()); workflowSubmitDialog.run(); if (workflowSubmitDialog.parmIsClosedOK()) { recId = args.record().RecId; SalesTable = args.record(); // Get comments from the submit to workflow dialog. note = workflowSubmitDialog.parmWorkflowComment(); try {
ttsbegin;
workflowCorrelationId = Workflow::activateFromWorkflowType(workflowTypeName, recId, note, NoYes::No); SalesTable.CreditLimitApprovalStatus = SalesCreditLimitApprovalStatus::Submitted; // Send an Infolog message. info("Submitted to workflow.");
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-7
Development III in Microsoft Dynamics® AX 2012 ttscommit; } catch(exception::Error) { info("Error on workflow activation."); }
}
} args.caller().updateWorkFlowControls();
Create a Workflow Approval A workflow element may contain a number of outcomes. The workflow may be approved, rejected, returned or a change may be requested. The Workflow Approval element determines which of these outcomes is allowed and what happens in the event of each outcome. Each outcome can trigger the execution of business logic by specifying a menu item for each item.
Demonstration: Creating a Workflow Approval Perform the following steps to create a workflow approval element and set key properties. 1. 2. 3. 4. 5. 6.
6-8
Open the AOT. Expand the Workflow node. Right-click on Approvals and select Add-ins > Approval wizard. Click Next. Enter SalesCLApproval in Name. Enter SalesCreditLimitApprDocument in Workflow document.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow 7. Enter Overview in Document preview field group. 8. Enter SalesTableListPage in Document menu item.
FIGURE 6.3 WORKFLOW APPROVAL
9. Click Next. 10. Click Finish. A development project with a number of newly created elements is displayed. 11. Drag SalesCLApproval approval to the Supported elements node on the SalesCreditLimitAppr workflow type. 12. Save your changes to the SalesCreditLimitAppr workflow type
Create Event Handlers Event handlers are used to execute business logic at specific points in the life of a workflow. They can be implemented at the workflow level, for example when the workflow is started or completed, or at an element level, for example when anyone approves or rejects a step in the approval. Event handlers are implemented by creating a class that implements one or more of the EventHandler interfaces. The interfaces at the workflow level are as follows: Event
Description
WorkflowStarted EventHandler
This event occurs when the workflow instance starts.
WorkflowCompleted EventHandler
This event occurs when the workflow instance ends after it is completed.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-9
Development III in Microsoft Dynamics® AX 2012 Event
Description
WorkflowCanceled EventHandler
This event occurs when the workflow instance ends after it is canceled. Use this event handler to perform any clean up operations needed.
WorkflowConfig DataChangeEvent Handler
This event occurs when a workflow changes. Use this event handler to identify when a workflow has changed. For example, if you create an association between the application data and a workflow, this event would be raised if the workflow was deleted or updated so that application code could be invoked or respond in some way.
Demonstration: Write code for Event Handler The wizards run in the previous demonstrations create event handler classes that need to be completed. This can include basic steps to set the status of the approval, or include more complex business logic. The following code needs to be added to the completed method of the SalesCreditLimitApprEventHandler class. public void completed(WorkflowEventArgs _workflowEventArgs) { SalesTable SalesTable; select forupdate SalesTable where SalesTable.RecId == _workflowEventArgs.parmWorkflowContext().parmRecId(); if(SalesTable.RecId) { SalesTable.CreditLimitApprovalStatus = SalesCreditLimitApprovalStatus::Approved; SalesTable.write(); } }
Element Level Event Handlers The event handler interfaces at the workflow element level are as follows:
6-10
Event
Description
WorkflowElement StartedEventHandler
This event occurs when the task or approval starts. For approvals, you can use this event to transition the workflow document state from Submitted to PendingApproval.
WorkflowElement CanceledEventHandler
This event occurs when the task or approval is canceled. For approvals, you can use this event to transition the workflow document state from the current state to Canceled.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow Event
Description
WorkflowElement CompletedEvent Handler
This event occurs when the task or approval is completed. For approvals, you can use this event to transition the workflow document state from PendingApproval to Approved.
WorkflowElement ReturnedEventHandler
This event occurs when the task or approval is returned to the originator. For approvals, you can use this event to transition the workflow document state from the current state to RequestChange.
WorkflowElemChange RequestedEvent Handler
This event occurs when an approver requests a change to the task or approval. For approvals, you can use this event to transition the workflow document state from PendingApproval to RequestChange.
Demonstration: Add Element Level Event Handlers Perform the following steps to add element or outcome level event handlers. 1. In the AOT locate the class SalesCLApprovalEventHandler. This class was created by the Approval element wizard. 2. Add the following code in the returned method public void returned(WorkflowElementEventArgs _workflowElementEventArgs) { SalesTable SalesTable; ttsbegin; select forupdate SalesTable where SalesTable.RecId == _workflowElementEventArgs.parmWorkflowContext().parmRecId() ; SalesTable.CreditLimitApprovalStatus = SalesCreditLimitApprovalStatus::Rejected; SalesTable.update(); ttscommit; }
3. Save your changes to the class. 4. In the AOT, right-click on the AOT node and select Incremental CIL generation from X++
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-11
Development III in Microsoft Dynamics® AX 2012 NOTE: Code run in batch jobs must first be compiled to the Common Intermediate Language (CIL) of the .NET framework. This increases the performance of the batch job.
Author a Workflow Now that you have created a workflow type and enabled it on a form, you can configure it for use.
Demonstration: Authoring a Workflow Perform the following steps to configure a workflow from the main menu. 1. The workflow category we created in the first procedure needs to be added to the main menu. Create a new display menu item called WorkflowConfigurationSales. 2. Set the label to Sales workflows. 3. Set the object to WorkflowTableListPage. 4. Set the EnumTypeParameter to ModuleAxapta. 5. Set the EnumParameter to SalesOrder. 6. Add the new menu item to SalesAndMarketting > Setup menu. 7. In the property sheet for the new node in the menu, set the property IsDisplayedInContentArea to Yes. 8. Save your changes to the menu. 9. Open the main menu and select Sales and Marketting > Setup > Sales workflows. 10. Click New. 11. Select SalesCreditLimitApprType and click Create workflow. The workflow editor window opens. 12. Drag SalesCLApprovalApproval approval from the Workflow elements window to the Workflow window.
6-12
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow 13. Drag the bottom node of the Start box to the top node of the Approval box. 14. Drag the bottom node of the Approval box to the top node of the End box.
FIGURE 6.4 WORKFLOW EDITOR
15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25.
Double click on the Approval box. Click Step 1 Click Assignment. On the Assignment type tab, select User On the User tab, double click on Sammy Click on Basic Settings and then enter a subject and instructions for the approval Click on Close Click on Save and Close Enter some notes for the new workflow if you wish. Select to Activate the new version Click OK.
Demonstration: Test the Workflow Perform the following steps to test the workflow by setting a credit limit and creating a sales order. 1. In the application workspace, navigate to Accounts receivable > Common > Customers > All customers. 2. Select a customer and click Edit.
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-13
Development III in Microsoft Dynamics® AX 2012 3. In the Credit and collections fasttab, set a credit limit. 4. Close the Customers form. 5. Go to Sales and marketting > Common > Sales orders > All sales orders. 6. Click New sales order. Enter the customer account modified that you modified the credit limit for and click ok. 7. Enter items and quantities in the sales lines so that the balance of the customer plus the total amount on the lines is greater than the credit limit. 8. The workflow Submit button and dialog should appear. 9. Click the Submit button and enter a comment. 10. Wait for the batch job to process the workflow request. This should take one to two minutes. 11. Select Actions > History. You will see that the document is waiting for approval by the person you assigned to approve it. 12. Logon to windows as the Sammy using the Switch User option on the Start menu. 13. Start the Microsoft Dynamics AX client. 14. Open the Sales order form. 15. Click the workflow Actions button and select Approve. 16. Wait one to two minutes for the workflow engine to process the approval. 17. The workflow has now been approved.
6-14
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow
Lab 6.1 - Add Another Condition to the Submit Action Scenario Isaac has been asked to ensure that, once a credit limit has been reached, the sales order cannot be posted until the workflow has been approved.
Challenge Yourself! Add conditions to the posting functions on the sales order form that will prevent posting to picking, packing or invoicing until the workflow has been approved. If the credit limit has not been reached, then the postings should be allowed.
Step by Step 1. Add the following method CanPostCreditLimit to the salesTable table. 2. Add the following code to the methods canPickingListBeUpdate(), canPackingSlipBeUpdated() and canInvoiceBeUpdated() in the salesTableType class. boolean canPostCreditLimit() { amountMST creditBalance; custTable custTable; ; if (this.CreditLimitApprovalStatus == SalesCreditLimitApprovalStatus::Approved) return true; if (this.CreditLimitApprovalStatus == SalesCreditLimitApprovalStatus::Rejected || this.CreditLimitApprovalStatus == SalesCreditLimitApprovalStatus::Submitted) return false; custTable = this.custTable_InvoiceAccount(); if (!custTable.CreditMax) return true; creditBalance = custTable.CreditMax custTable.balanceMST(); if (this.amountRemainSalesFinancial() + this.amountRemainSalesPhysical() < creditBalance) return true; return false; }
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-15
Development III in Microsoft Dynamics® AX 2012 boolean canPickingListBeUpdated() { ...... ok = ok && salesTable.canPostCreditLimit(); return ok; } boolean canPackingslipBeUpdated() { ...... ok = ok && salesTable.canPostCreditLimit(); return ok; } boolean canInvoiceBeUpdated() { ...... ok = ok && salesTable.canPostCreditLimit(); return ok; }
6-16
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow
Lab 6.2 - Enable Resubmit Scenario Isaac is required to ensure the workflow can be resubmitted after it has been rejected.
Challenge Yourself! When a workflow is rejected, it can be resubmitted. Modify the Submit to Workflow class so that it can resubmit the workflow after a rejection Use the PurchReqWorkflow class as model.
Step by Step 1. Find the class SalesCLApprovalResubmitActionMgr. 2. Modify the Main method and add a new method Resubmit as shown in the following code: public static void main(Args _args) { SalesCLApprovalResubmitActionMgr SalesCLApprovalResubmitActionMgr = new SalesCLApprovalResubmitActionMgr(); SalesCLApprovalResubmitActionMgr.resubmit(_args); } private void resubmit(Args _args) { // Variable declaration. recId _recId = _args.record().RecId; WorkflowCorrelationId _workflowCorrelationId; // Hardcoded type name WorkflowTypeName _workflowTypeName = workflowtypestr(SalesCreditLimitAppr); // Initial note is the information that users enter when they // submit the document for workflow. WorkflowComment _initialNote =""; WorkflowWorkItemActionDialog WorkflowWorkItemActionDialog; SalesTable SalesTable; ;
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-17
Development III in Microsoft Dynamics® AX 2012 // Opens the submit to workflow dialog. workflowWorkItemActionDialog = WorkflowWorkItemActionDialog::construct( _args.caller().getActiveWorkflowWorkItem(), WorkflowWorkItemActionType::Resubmit, new MenuFunction(menuitemactionstr(PurchReqReSubmit), MenuItemType::Action)); workflowWorkItemActionDialog.run(); if (WorkflowWorkItemActionDialog.parmIsClosedOK()) { _recId = _args.record().RecId; SalesTable = _args.record(); // Get comments from the submit to workflow dialog. _initialNote = workflowWorkItemActionDialog.parmWorkflowComment(); try {
ttsbegin;
WorkflowWorkItemActionManager::dispatchWorkItemAction( _args.caller().getActiveWorkflowWorkItem(), _initialNote, curUserId(), WorkflowWorkItemActionType::Resubmit, _args.menuItemName(), false); SalesTable.CreditLimitApprovalStatus = SalesCreditLimitApprovalStatus::Submitted; // Send an Infolog message. info("Resubmitted to workflow.");
6-18
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow }
}
ttscommit;
catch(exception::Error) { info("Error on workflow activation."); }
_args.caller().updateWorkFlowControls(); }
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-19
Development III in Microsoft Dynamics® AX 2012
Summary The workflow system is highly configurable and flexible. By using Morph X and some standard code patterns, it can be enabled for virtually any part of the Microsoft Dynamics AX application. This lesson explores some of the possibilities the workflow system offers, and explores some of the different ways it can be used to cover most workflow requirements.
6-20
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow
Test Your Knowledge Test your knowledge with the following questions. 1. What application element is used to define to which module a workflow is applicable? ( ) Workflow type ( ) Workflow category ( ) A field in the workflow configuration ( ) SalesTable 2. What type of AOT element needs to be created to specify which tables will be processed by a workflow? ( ) Extended data type ( ) Class ( ) Form ( ) Query 3. Which three properties on a form design need to be modified to allow the form to use a workflow? ( ) WorkflowType ( ) WorkflowEnabled ( ) WorkflowDocument ( ) WorkflowDatasource
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-21
Development III in Microsoft Dynamics® AX 2012
Quick Interaction: Lessons Learned Take a moment and write down three key points you have learned from this chapter 1.
2.
3.
6-22
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
Chapter 6: Workflow
Solutions Test Your Knowledge 1. What application element is used to define to which module a workflow is applicable? ( ) Workflow type (•) Workflow category ( ) A field in the workflow configuration ( ) SalesTable 2. What type of AOT element needs to be created to specify which tables will be processed by a workflow? ( ) Extended data type ( ) Class ( ) Form (•) Query 3. Which three properties on a form design need to be modified to allow the form to use a workflow? (√) WorkflowType (√) WorkflowEnabled ( ) WorkflowDocument (√) WorkflowDatasource
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
6-23
Development III in Microsoft Dynamics® AX 2012
6-24
Microsoft Official Training Materials for Microsoft Dynamics® Your use of this content is subject to your current services agreement
View more...
Comments