30 December 2015

Query datasource FirstOnly property

How does query datasource FirstOnly property work and influence SQL Server query? It seems obvious, but in fact it is not.

Problem description
Analyse how a query is translated into SQL Server query based on FirstOnly property.

Solution
Based on the description in msdn FirstOnly property is a hint for database that only one record is required. Let's run several tests to analyse how it actually works. You can also jump to the conclusion section.

30 November 2015

Query datasource FetchMode property

FetchMode property sometimes was a magic solution for report development in Axapta 3.0. Since then I wanted to run several tests and describe the feature.

Problem description
Analyse how a query is translated into SQL Server query based on FetchMode property.

Solution
Based on the description in msdn FetchMode property is available on an embedded datasource and determines a relation between parent and child datasources.
FetchMode values are 1:1 and 1:n, but there is no description of them.
The property name and values can lead to an assumption:
1:1 - data for parent and child datasources is fetched simultaneously
1:n - data for parent and child datasources is fetched separately.
Running ahead I must say that the assumption is not precisely correct, but let's analyse it. You can also jump to the conclusion section.

26 October 2015

Party primary address non-covered cases

There is quite logical feature of party primary address in AX 2012, description of which I was not able to find on technet or msdn. Please find the details below.

Description
Only an address which is valid now and has no expiration can be set as Primary.
Only valid now address which never expires can be set as primary

It sounds logical: primary address must be valid and usually has indefinite lifetime.
However, there are use cases which do not fit.

Case 1. New primary address in future
A party informs about a new primary address starting from next month. The new address can be added, but can not be marked as primary in future. It means the user must remember the case and set primary on the new address later when it becomes valid.

Case 2. Primary address expiration
A party informs about business suspension for a period required to move to a new location. The case is more theoretical, but it is not possible to change Effective or Expiration on primary address:
Effective and Expiration can not be modified on party primary address

I believe it was a trade-off as such cases are rare and a solution is quite complicated. Address Effective and Expiration are defined in LogisticsPostalAddress table. Address Primary setting is defined in DirPartyLocation.IsPrimary and is duplicated in DirPartyTable.PrimaryAddressLocation.

Hints
I would like to suggest to discuss mentioned cases with customers, so everything is clear and there are no surprises.

30 September 2015

How to create missing records in another company

Sometimes in multi-company environment it is required to create records which are missing in another company. The first intention can be to use notexists join and crossCompany keyword to find missing records. Such approach has a problem, which is described in the previous post.

Description
Let's assume that the goal is to create records of company DEMF which are missing in company USRT:
Missing records example
An advanced solution will handle a set of source companies and a set of destination companies.

Steps to reproduce
1. Switch to DEMF company
2. Note existing Buyer groups (Inventory and warehouse management > Setup > Inventory > Buyer groups)
3. Switch to USRT company
4. Note existing Buyer groups
5. Run job
6. Compare Buyer groups

Hints
Look for examples in standard AX by searching while select crossCompany text in methods. Continue reading for the solution.

24 August 2015

Non-documented feature of cross-company keyword

My colleague asked my advise about cross-company query. The goal was to create missing records of customer specific table in another company. The query looked correct, however the tests failed.

Problem description
To make it simple let's assume that records of InventItemGroup table in company USMF must be created in company USRT (provided the records are missing in USRT company). It seems to be possible to use cross-company query like this:
InventItemGroup inventItemGroup, inventItemGroupJoin;
    
while select crossCompany inventItemGroup 
    where inventItemGroup.dataAreaId      == 'USMF'
    notExists join inventItemGroupJoin
    where inventItemGroupJoin.dataAreaId  == 'USRT'
       && inventItemGroupJoin.ItemGroupId == inventItemGroup.ItemGroupId
{
    // create new record in USRT company
}
This approach is wrong though. Are you also curious why? Let's analyse some specifics of cross-company queries.

Steps to reproduce
The following job can be used for tests and analysis:
static void CrossCompanyTest(Args _args)
{
    InventItemGroup inventItemGroup, inventItemGroupJoin;
    
    select generateonly forceLiterals crossCompany ItemGroupId 
        from inventItemGroup
        where inventItemGroup.dataAreaId      == 'USMF'
        notExists join inventItemGroupJoin
        where inventItemGroupJoin.dataAreaId  == 'USRT'
           && inventItemGroupJoin.ItemGroupId == inventItemGroup.ItemGroupId;
    
    info(inventItemGroup.getSQLStatement());
}

Hints
Try to modify predicates of join and use Query class instead of select statement.

15 July 2015

How unique index influences T-SQL statement

As it was mentioned in the previous post, T-SQL statement can contain additional fields which are not selected in X++ statement. Let's analyse on InventBatch table what exactly is added and why.

Problem description
Analyse how an unique index influences T-SQL statement.

Steps to reproduce
Run a test job:
static void Test_UniqueIndex_SQL(Args _args)
{
    InventBatch inventBatch;
    
    select generateonly description from inventBatch;

    info (inventBatch.getSQLStatement());
}

Hints
Change index properties on the table and re-run test job.

25 June 2015

Mysterious 101090 in T-SQL statement

Sometimes Transact-SQL (T-SQL) statement sent form AOS to SQL Server contains mysterious 101090 in a field list, even though select statement in X++ does not contain it. Also curious why?

Problem description
Find out what is 101090 in T-SQL statement.

Steps to reproduce
Run job like:
static void Test_101090_SQL(Args _args)
{
    InventBatch inventBatch;
    
    select generateonly description from inventBatch;

    info (inventBatch.getSQLStatement());
}

Hints
Try to select different system fields.

23 May 2015

How joins in X++ select statement are translated into T-SQL

It is not always obvious what request is actually executed on SQL Server. The most confusing is probably exists join in X++. Let's analyze how joins in select statement in X++ are translated into T-SQL statement sent to SQL Server.

27 April 2015

How to check if record in valid time state table is valid now

Working on data synchronisation with legacy system I faced interesting question in valid time state table area. Please find the details below.

Problem description
Create a method to check whether a record in valid time state table is valid now.

Steps to reproduce
1. Find or create new valid time state table
2. Create 3 records: valid in past, valid now, valid in future.
3. Test the method on each record

Hints
There are at least two solutions:
1. Try to select the same record from database. Kernel adds Date or DateTime ranges and nothing is returned if the record is not valid now.
2. Compare values of ValidFrom and ValidTo fields with now. Analyse and define now for Date and DateTime.

23 March 2015

What Date and DateTime does kernel use for valid time state tables

I had to check whether a record in valid time state table is valid now and I got curious about what Date and DateTime values are actually used by kernel.  The answer seemed to be obvious, but in fact it was not. Please find the details below.

Problem description
There are several cases when kernel generates values for valid time state tables:
- ValidFrom field default value set by kernel when a new record is created
- ValidFrom and ValidTo ranges added by kernel into a select statement.
Let's analyse them.

Questions to analyse
1. Is session date and time used?
2. Is client machine date and time used?
3. Is user time zone used?

Hints
Create two valid time state tables with ValidTimeStateFieldType property set to Date and UtcDateTime. Try to change session date and time, client machine date and time, user time zone and compare default values of new record in different cases.

16 March 2015

Non-documented feature of Dynamic property on query datasource Fields

While analysing performance issues I faced non-documented feature of Dynamic property on query datasource Fields. I made simplified and theoretical example to demonstrate problem and solution.

Problem description
More information than expected is returned form database when Fields node on query datasource has no field and Dynamic property is set to No.

Steps to reproduce
1. Create a new query in AOT like on screenshot below:
Query with no field in Fields and Dynamic property set to No

2. Create a test job to get query statement in AX and to invoke SQL statement
static void CustTableBankAccountCountry(Args _args)
{
    Query       q = new Query(queryStr(CustTableBankAccountCountry));
    QueryRun    qr;
    
    info (q.dataSourceNo(1).toString());
    
    qr = new QueryRun(q);
    qr.next();
}

3. Setup SQL Server Profiler to catch test statement
Note: for test it is enough to trace only SP:SmtCompleted event and filter results by CustTable (I plan to write a separate post on SQL Server Profiler hints for Dynamics AX tracing)
SQL Server Profiler setup for Dynamics AX

4. Run test job

5. Compare query statement in AX with actual statement in SQL
The following statement is built in AX:
SELECT AccountNum FROM CustTable(CustTable) 
OUTER JOIN  FROM CustBankAccount(CustBankAccount) 
ON CustTable.AccountNum = CustBankAccount.CustAccount 
OUTER JOIN CountryRegionId FROM LogisticsPostalAddress(LogisticsPostalAddress) 
ON CustBankAccount.Location = LogisticsPostalAddress.Location

The following statement is traced in SQL:
SELECT T1.ACCOUNTNUM,T1.RECID,
T2.ACCOUNTID,T2.CUSTDOMNUMBER_BE,T2.NAME,T2.ACCOUNTNUM,T2.BANKCIN,
T2.BANKACCOUNTTYPE,T2.LOCATION,T2.CURRENCYCODE,T2.REGISTRATIONNUM,
T2.PHONE,T2.TELEFAX,T2.CONTACTPERSON,T2.EMAIL,T2.URL,T2.TELEX,
T2.CELLULARPHONE,T2.PHONELOCAL,T2.CUSTACCOUNT,T2.BANKGROUPID,
T2.CUSTPAYMENTTEXTCODE,T2.SWIFTNO,T2.MSGTOBANK,T2.EXCHRATE,
T2.EXCHRATEREF,T2.BANKCONTRACTACCOUNT,T2.PAGER,T2.SMS,T2.BANKCODETYPE,
T2.BANKIBAN,T2.CORRESPONDENTBANKNAME_LT,T2.CORRESPONDENTBANKADDRESS_LT,
T2.CORRESPONDENTBANKSWIFT_LT,T2.INTERMEDIATEBANKNAME_LT,
T2.INTERMEDIATEBANKADDRESS_LT,T2.INTERMEDIATEBANKSWIFT_LT,
T2.CORRESPONDENTBANKACCOUNT_LT,T2.INTERMEDIATEBANKACCOUNT_LT,
T2.FOREIGNBANK_RU,T2.FOREIGNACCOUNT_RU,T2.FOREIGNSWIFT_RU,
T2.BANKSORTCODE_RU,T2.CORRACCOUNT_W,T2.LVDEFAULTBANK,T2.BANKCONSTANTSYMBOL,
T2.SPECIFICSYMBOL,T2.RECVERSION,T2.PARTITION,T2.RECID,
T3.COUNTRYREGIONID,T3.RECID 
FROM CUSTTABLE T1 
LEFT OUTER JOIN CUSTBANKACCOUNT T2 
ON (((T2.PARTITION=@P1) AND (T2.DATAAREAID=@P2)) AND (T1.ACCOUNTNUM=T2.CUSTACCOUNT)) 
LEFT OUTER JOIN LOGISTICSPOSTALADDRESS T3 
ON ((T3.PARTITION=@P3) AND ((T2.LOCATION=T3.LOCATION) 
AND ((T3.VALIDFROM<=@P4) AND (T3.VALIDTO>=@P5)))) 
WHERE ((T1.PARTITION=@P6) AND (T1.DATAAREAID=@P7)) 
ORDER BY T1.ACCOUNTNUM

No field is expected to be selected from CustBankAccount table based on query definition. Query statement in AX proves it, but actual SQL statement selects and returns all fields from CustBankAccount. As no field is actually required from CustBankAccount table, such useless select should be omitted.

Hints
Try to add any field into Fields node.

27 February 2015

User friendly date effective association collection

Recently I got a request to provide a function to classify a team (customer specific date effective classification). The goal was to make the process simple and user friendly. My suggested solution was accepted. I want to share it, so probably it can be re-used.

Problem description
Several classifications are defined. Only one classification can be assigned to a team at any given time. The current classification is presented in Teams form. If current classification is not defined then Classification field is empty. The user can simply set new classification by selecting a new value in lookup. If new classification is set, previous classification is made ineffective. Future classification is taken into consideration for new classification validity period. The user can manually update classifications effective dates in Party form.

Steps to reproduce
1. Define classifications
To simplify it - let's assume that "Class1", "Class2", "Class3" are defined
Classification defined for date effective association collection


2. Add "Class1" in Party form for new "Team 1"
Effective (ValidFrom) set equal to (today - 7 days), Expiration (ValidTo) left without changes.
Adding Class1 for new Team1


3. Add "Class3" as future period in Party form for the "Team 1"
Effective (ValidFrom) is equal to (today + 7 days), Expiration (ValidTo) left without changes. Click Yes in dialog and note that Expiration (ValidTo) is updated on "Class1"
Adding Class3 for Team1

Expiration on Class1 is updated for Team1


4. Open "Team 1" and make sure that current classification is "Class1"
Current classification on Team1


5. Set current classification to "Class2" for "Team 1"
Set Class2 as Current classification on Team1


4. Three classifications are defined in Party form
"Class1" valid from (today - 7 days) to (today - 1 day)
"Class2" valid from (today) to (today + 6 days)
"Class3" valid from (today + 7 days) to (max date)
Team1 classifications

Hints
Write down all possible periods to design the algorithm.

16 January 2015

How does date effective CreateNewTimePeriod work

Two different descriptions of CreateNewTimePeriod update mode lead me to the question - how does it actually work?

Description 1 (msdn)
When you change a field value from a valid time state table, the current record in the table is closed and a new record is created. The current date, or current date and time, populate the ValidTo field of the closed record and the ValidFrom field of the new record.

Description 2 (Inside Microsoft Dynamics AX 2012)
The date-effective framework creates a new record with updated values, and updates the ValidTo of the edited record to a value of ValidFrom -1 of the newly inserted record.

Problem description
What sequence of methods is used in CreateNewTimePeriod update mode?

Steps
1. Open DirPartyTable form
2. Add a new address
3. Modify the address

Hints
Add infolog messages to insert and update methods of LogisticsPostalAddress table.

02 January 2015

Alert and database log coexistence

While testing Database log functionality I found a tricky behaviour which can cause unwanted results.

Problem description
Alert is not generated, although Alert rule is enabled.

Steps
1. Create a new alert rule for Customers insert event
Accounts receivable > All customers > select Customer account field and right click > Create alert rule...

2. Add Customers table to Database log using a standard wizard
System administration > Setup > Database > Database log setup > New > Next > Open General ledger and select Customers

3. Create a new customer

4. Run Change based alerts job
System administration > Periodic > Alerts > Change based alerts

5. No alert is generated

Below you will find hints and solution description, however challenge yourself and try to repeat described steps and make assumptions :-)

Hints
Compare data in DatabaseLog table for CustTable before and after step 2.

01 January 2015

Welcome to Dynamics AX Hints!

Hello,

My name is Oleg Abapolov. I have been working with Dynamics AX since 2005, but I still find something new, undocumented or unknown. I decided to write a blog about such interesting findings.

In each post I will describe some problem as well as steps to reproduce it. Hints will help to guess one of possible solutions, which will be hidden under cut. My goal is to stimulate you to think about a solution and if your solution is different from posted, I would kindly ask you to share it.

As today is a special day I want to wish everybody a Happy New Year!
In Dynamics AX world this year is expected to be very interesting, especially with release of next version.
Let's make it more fun and exciting by posting and discussing findings and solutions! :-)