Wednesday, September 5, 2018

Dynamics 365 : How to use tempDB tables in queries


I had a requirement where a TempDB table is used in the Query along with other regular tables. 

In order achieve this we need to use the method setRecord on QueryRun to assign a record from the TempDB table. 

static void tempDBQuery(Args _args)
{
MyTmpIdList tmpIdList1, tmpIdList2;


Query query = new Query();

tmpIdList1.RefRecId = 1;
tmpIdList1.insert();

tmpIdList1.RefRecId = 2;
tmpIdList1.insert();

tmpIdList2.RefRecId = 1;
tmpIdList2.insert();

tmpIdList2.RefRecId = 2;
tmpIdList2.insert(); 

QueryBuildDataSource qbds1 = query.addDataSource(tableNum(MyTmpIdList));
QueryBuildDataSource qbds2 = qbds1.addDataSource(tableNum(MyTmpIdList));

qbds2.relations(false);
qbds2.joinMode(JoinMode::InnerJoin);
qbds2.addLink(fieldNum(MyTmpIdList, RefRecId), fieldNum(MyTmpIdList, RefRecId));

QueryRun  queryRun = new QueryRun(query);
queryRun.setRecord(tmpIdList1, 1);
queryRun.setRecord(tmpIdList2, 2);

while(queryRun.next())
{
tmpIdList1 = queryRun.getNo(1);
tmpIdList2 = queryRun.getNo(2);

info(strFmt("%1 %2 %3 %4", tmpIdList1.RefRecId, tmpIdList1.RecId, tmpIdList2.RefRecId, tmpIdList2.RecId));
}
}
 

In the example the table MyTmpIdList is used twice in the query and hence the occurrence is also specified when calling the setRecord method. However in case there are multiple TempDB tables involved in the Query without any occurrence/repetition , you may just pass the TempDB table records for each TempDB tables in the query by calling the setRecord method.

Friday, November 17, 2017

Dynamics 365 - Sub reports


During our upgrade, we found our sub reports stopped working. I tried looking for answers on the web and community forums, but none of them helped me solve my problem.

So I started trying all wiered things which helped me resolve my issue at the end.

Here are the things we need to keep in mind during the creation of sub reports in Dynamcis 365,

  1. The host report and the sub report must follow the same data pattern. For example, if the host report uses "Query" as data provider, all the sub reports must use "Query" as a data provider and vice versa.
  2. All the parameters in the sub report including standard AX_Parameters must be mapped in the sub report parameter mapping.

    AX_PartitionKey = [@AX_PartitionKey] 

Dynamics 365 - Extensibility on methods in a subclass that are declared in the base class


We stumbled upon this while reviewing the extensibiliy requests that we have to submit to Microsoft. As you probably all know, we can no longer overlayer in D365, so we have to adopt the new, extensibility-based approach.

In the class 'AssetProposalDepreciation' we have a customization in the 'run' method. While reviewing this customization, we reasoned that the customization can be moved to a post event on the method 'initFirstDate', which is called just before our customization. However, the class 'AssetProposalDepreciation' is a subclass of the class 'AssetProposal', and the method 'initFirstDate' is declared in the base class (and not overridden in the subclass).

Before I continue, I should mention that the approach below has been tested using the community tech preview (CTP) of D365 fall release 2017.

Attempt 1


The first thing we tried was creating an event handler class with a post event on the method 'initFirstDate' for the class 'AssetProposalDepreciation':

class MyEventAssetProposalDepreciation
{
[PostHandlerFor(classStr(AssetProposalDepreciation), methodStr(AssetProposalDepreciation, initFirstDate))]
public static void AssetProposalDepreciation_Post_initFirstDate(XppPrePostArgs args)
{
//
info("Test"); // Let's assume that this is our customization.
//
}
}

The code above resulted in a compile error; it is not possible to create a post event on the method 'initFirstDate', as it is not declared in the class 'AssetProposalDepreciation' (it is declared in the base class 'AssetProposal').

Attempt 2


The next thing we tried was using Microsoft's new Chain of Command feature. For those of you who are unfamiliar with this new feature, this short video explains it fairly well: https://blogs.msdn.microsoft.com/mfp/2017/07/04/extensible-x-chain-of-command/. So, we created the following extension class:

[ExtensionOf(classStr(AssetProposalDepreciation))]
class MyAssetProposalDepreciationClass_Extension
{
    // The method signature was copied from the class 'AssetProposal'.
    public AssetTransDate initFirstDate(AssetTransDate _assetTransDate, AssetId _assetId) 
    {
        AssetTransDate ret = next initFirstDate(_assetTransDate, _assetId);

        //
        info("Test"); // Again, this is our customization.
        //

        return ret;
   }
}

The code above works :-). 


Whenever the method 'initTransDate' is called in the class 'AssetProposalDepreciation' (e.g., this.initTransDate(...) ), the line 'AssetTransDate ret = next initFirstDate(_assetTransDate, _assetId);' in the extension class is executed first, then the original code in the method 'initFirstDate' is executed (because of the 'next' statement; the return type is assigned to the variable 'ret'), and finally our customization code is executed. 

So, using the Chain of Command feature, we have basically 'mimmicked' a post event. 

Dynamics 365 - More than one form was opened at once for the lookup control


In case you want to override the default lookup on a field , pay attention to cancelSuperCall() call as well.


If we don’t call this method then a call is first made to the default lookup and then your lookup logic will be executed.

If we don’t cancel the super call , we end up with an error “More than one form was opened at once for the lookup control.” Here is an example of how to cancel the super call.

[FormControlEventHandler(formControlStr(ProjTable, MyTable_MyField), FormControlEventType::Lookup)]
public static void 
MyTable_MyFieldCTRL_OnLookup(FormControl _sender, FormControlEventArgs _eventArgs)
{
    FormControlCancelableSuperEventArgs eventArgs = _eventArgs as FormControlCancelableSuperEventArgs;
    eventArgs.CancelSuperCall();


    MyHelper::lookupMyField(_sender);
}

Dynamics 365 - Comparison operators on enum values


As you know, in D365 the comparison (<, >, <=, >=) operators are no longer supported in certain scenarios that have to do with base enums. When you come across a situation like that, there are three different ways in which you can rewrite it:

Example situation
Select all SalesLines within my order that have a status greater than ‘backorder’.

select firstonly salesLine
where salesLine.SalesId = _salesId
&& salesLine.SalesStatus > SalesStatus::Backorder; // no longer supported in D365

Rewrite strategy #1 – explicit inclusion


Explicity mention each of the enum values you wish to include in your selection.

select firstonly salesLine
where salesLine.SalesId = _salesId
&& (salesLine.SalesStatus == SalesStatus::Delivered ||
salesLine.SalesStatus == SalesStatus::Invoiced ||
salesLine.SalesStatus == SalesStatus::Canceled);

Rewrite strategy #2 – explicit exclusion


Explicity mention each of the enum values you do NOT wish to include in your selection.

select firstonly salesLine
where salesLine.SalesId = _salesId
&& salesLine.SalesStatus != SalesStatus::None 
&& salesLine.SalesStatus != SalesStatus::Backorder;

Rewrite strategy #3 – ordered list


Use the “ordered list pattern”. This requires a lot of additional overhead so this is not the preferred way. In case you feel rewrite strategy 1 & 2 are not suitable in your particular scenario, you can consider using this third approach. Also, in situations where we have added our own enum elements to a standard AX enum, and the standard codes uses a comparison operator on that enum somewhere, this third approach is the only way that will work. Check these standard AX classes for an example (PU8 and onwards): InventTestBlockProcessComparer, InventTestBlockProcessOrderedList, InventQualityManagementBlock

Wednesday, November 15, 2017

Dynamics 365 - How to save your queries in AX7 / D365 / DAXFO365 / "The new Ax"

As you might have noticed, the nice query editor is gone, and with it, the ability to store your grid filters on forms. This is what I am talking about (From AX2012):



To similar functionality in Ax7, you need to enable URL Manipulation under the user settings (See attached screenshot at the bottom where it makes no sense).


Once this has been enabled, you will get a URL that contains your filter in a hashcode. In your browser, you can then set a bookmark to it, and reuse, or share that filter with other users.

Dynamics 365 - Display method caching

There are five different options regarding display method caching


  1. No caching – not preferred, unless you have a compelling reason 
  2. 'CacheDataMethod’ property on form controls 
  3. Add a call to ‘cacheAddMethod’ in FormDataSource’s init method
  4. Add the ‘SysClientCacheDataMethodAttribute’ to the display method – even though the compiler will allow you to put this attribute on any display method, it only actually works on display methods that are defined on a table (not on forms, data sources, or classes)
  5. Custom caching mechanism -- code it yourself

Use the attached flowchart to determine the correct caching strategy for your scenario.




Edit methods should be cached using the same strategy.

Always set the updateOnWrite parameter to true when you’re adding the SysClientCacheDataMethodAttribute, or calling cacheAddMethod on a form data source. In certain situations it would be better not to refresh during updates (like in forms where the table in question is used as a read-only data source), however this approach often leads to the introduction of errors when other developers/parties customize the method or add it to new forms where conditions are different.