Thirteen years with Axapta and finally got it today!
Friday, May 7, 2021
Thursday, April 22, 2021
Maximum Size for Business Events
Business Events help to integrate D365FO with other systems; they are supposed to be specific and small. But how exactly small should they be? What is the maximum size for one message?
If we take a look at the code, we will see that the maximum is driven by the type of Business Event End Point:
For example, BusinessEventsServiceBusAdapter even implements some logic here.
As per Flow and HTTP, it is defined as 1MB minus 4KB for the header.
So, we have two conclusions here:
- we have to check these hard-coded limits directly in the code, as they may be changed in future;
- if you need to send a chunk of information bigger than the maximum size, you'd better revise your solution. For example, instead of sending a file you can send just its Azure Storage locator;
Another option can be developing your own End Point.
Some useful links.
How to develop Business Events
Example of using Microsoft Power Automate (Flow) to integrate with D365FO via Business Events
Monday, April 12, 2021
Switch in View Computed column
If you need to compare multiple fields in your view while populating a computed column, take into consideration that the standard implementation of method SysComputedColumn::switch uses a map enumerator.
public static client server str switch(str _controlExpression, Map _comparisonExpressionMap, str _defaultExpression) { MapEnumerator mapEnumerator; str caseExpression = ''; caseExpression ='CASE ' + _controlExpression; mapEnumerator = _comparisonExpressionMap.getEnumerator(); while (mapEnumerator.moveNext()) { caseExpression += ' WHEN ' + mapEnumerator.currentKey() + ' THEN ' + mapEnumerator.currentValue(); } caseExpression += ' ELSE ' + _defaultExpression; caseExpression += ' END'; return caseExpression; }
It means that your given order will be replaced in final SQL clause by alphabetical one of your keys.
CASE WHEN (T4.PROJID1) != ('') THEN T4.PROJNAME1 WHEN (T4.PROJID2) != ('') THEN T4.PROJNAME2 WHEN (T4.PROJID3) != ('') THEN T4.PROJNAME3 ELSE T4.PROJNAME END
For example, we want to populate field mgcProjInvoiceGLTransView.ParentProjectName for a given project id up to three level up in the project hierarchy. Let's assume that they are prepopulated in mgcProjInvoiceOnAccView view: projId1 is the parent of projId, projId2 is the parent of projId1, and so on.
Once we reference this computed column to the following method, we will get a new order in CASE construction on the SQL side.
private static str projNameParent() { str ret; SysComputedColumnBase::switch cannot keep a given order while using Map enumerator; so we put the SQL string as a string constant tableName viewName = identifierStr(mgcProjInvoiceGLTransView); tableName tableName = identifierStr(mgcProjInvoiceOnAccView); str compareValue = ''; Map comparisonExpressionMap = SysComputedColumn::comparisionExpressionMap(); str fieldNameProjName3 = SysComputedColumn::returnField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjName3)); str fieldNameProjName2 = SysComputedColumn::returnField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjName2)); str fieldNameProjName1 = SysComputedColumn::returnField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjName1)); str fieldNameProjName = SysComputedColumn::returnField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjName)); comparisonExpressionMap.insert( SysComputedColumn::notEqualExpression( SysComputedColumn::comparisonField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjId3)), SysComputedColumn::comparisonLiteral(compareValue)), fieldNameProjName3); comparisonExpressionMap.insert( SysComputedColumn::notEqualExpression( SysComputedColumn::comparisonField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjId2)), SysComputedColumn::comparisonLiteral(compareValue)), fieldNameProjName2); comparisonExpressionMap.insert( SysComputedColumn::notEqualExpression( SysComputedColumn::comparisonField(viewName, tableName, fieldStr(mgcProjInvoiceOnAccView, ProjId1)), SysComputedColumn::comparisonLiteral(compareValue)), fieldNameProjName1); ret = SysComputedColumn::switch( '', comparisonExpressionMap, fieldNameProjName); return ret; }
Of course, it always returns the name of the parent on the first level, even though it had its own parent, which is incorrect result.
Therefore, the easiest way is to replace the aforementioned construction with a simple string.
private static str projNameParent() { str ret; ret = "CASE WHEN (T4.PROJID3) != ('') THEN T4.PROJNAME3 WHEN (T4.PROJID2) != ('') THEN T4.PROJNAME2 WHEN (T4.PROJID1) != ('') THEN T4.PROJNAME1 ELSE T4.PROJNAME END"; return ret; }
Wednesday, March 31, 2021
Corrupted copy of SSRS report
Sometimes after copying an existing SSRS report or even just a part of its design, parser starts encountering strange errors. For example:
Unidentifiable substring 'Value' in expression. the parser reported error message
There must be something wrong in the mechanism responsible for copying text boxes. In most case, it replaces simple function names with predicates like Microsoft.Value etc. In my case it turned out to be even worse: some bug in strings concatenation; no matter what.
The problem here is that you have no hint about the object name, where such a non-conform string was added. You can try your textboxes one by one by checking all their properties with functions inside: visibility, font, border etc, even labels for its placeholder! And it can become a nightmare when your report contains dozens of them.
Fortunately there is a some workaround. You can open XML files for both of the original report and your copy of it in Notepad++ and run Compare over them (Install this Compare plugin).
Then scroll down to the first bad guy: a couple of strings above you will see its name.
Go back to the editor, find this textbox.
Now check its corrupted property function expressions and fix it.
Friday, January 22, 2021
D365FO data entities with financial dimension requires special field names
While creating data entities using financial dimensions, do not forget that those are backed in background by special classes via SysDataEntityPersister class
Those latter help resolving selected values (similar to what we see in segmented entry in forms).
Each class is meant to serve a particular EDT, for example
/// <summary> /// The <c>LedgerAccountDimensionDataEntityResolver</c> class resolves data entity ledger account values. /// </summary> [ System.ComponentModel.Composition.ExportMetadataAttribute("DimensionSFKType", identifierstr(LedgerDimensionAccount)), System.ComponentModel.Composition.ExportAttribute("Microsoft.Dynamics.AX.DimensionDataEntitySFKFieldResolver") ] public class LedgerAccountDimensionDataEntityResolver extends DimensionDataEntitySFKFieldResolver { Name accountStructure;
...
In order to let it work correctly, some fields in your data entity must have particular names.
public class DimensionDataEntityConstants { public static const str HasDimensionDataSourcesCustomPropertyName = '__HasDimensionDataSources__'; public static const str ProviderCacheName = 'DimensionDataEntitySFKProviderCacheName'; public static const str AccountStructureSuffix = 'AccountStructure'; public static const str DisplayValueSuffix = 'DisplayValue'; public static const str DisplayValueTypeName = 'DimensionDisplayValue'; }
Friday, January 15, 2021
How to delete obsolete values in ENUMVALUETABLE table X++
Following up this old issue with extensions to enums, I propose to run this class, if you cannot delete added values (grr* in my case) directly in SQL Studio. I am not sure if it is going to work in PROD. Please use it at your own risk.
class ENUMVALUETABLEDelete { public static void main(Args _args) { if(!Box::confirm("Would you like to delete all grr* values of SysPolicyRuleTypeEnum type in ENUMVALUETABLE? (If not, just print them")) { ENUMVALUETABLEDelete::printRecords(); return; } ENUMVALUETABLEDelete::deleteRecords(); info("all related records have been deleted. Synchonize DB now!"); } private static void deleteRecords() { Connection conn; SqlStatementExecutePermission permission; str sqlSelect = @"SELECT t.RECID from ENUMVALUETABLE as t join ENUMIDTABLE on enumid = id and ENUMIDTABLE.NAME = 'SysPolicyRuleTypeEnum' and t.NAME like 'grr%'"; str sqlDelete = strFmt("%1 (%2)", "DELETE FROM ENUMVALUETABLE WHERE RECID IN ", sqlSelect); permission = new SqlStatementExecutePermission(sqlDelete); conn = new Connection(); permission.assert(); //conn.transactionScopeBegin(); Statement statement = conn.createStatement(); int result = statement.executeUpdate(sqlDelete); str errText = statement.getLastErrorText(); if(errText) { Info(errText); } // the permissions needs to be reverted back to original condition. CodeAccessPermission::revertAssert(); } private static void printRecords() { Connection conn; SqlStatementExecutePermission permission; str sqlSelect = @"SELECT t.RECID, t.ENUMID, t.ENUMVALUE, t.NAME from ENUMVALUETABLE as t join ENUMIDTABLE on enumid = id and ENUMIDTABLE.NAME = 'SysPolicyRuleTypeEnum' and t.NAME like 'grr%'"; permission = new SqlStatementExecutePermission(sqlSelect); conn = new Connection(); permission.assert(); Statement statement = conn.createStatement(); ResultSet results = statement.executeQuery(sqlSelect); str errText = statement.getLastErrorText(); if(errText) { Info(errText); } int enumId; str name; int i = 1; while (results.next()) { enumId = results.getInt(3); name = results.getString(4); Info(strFmt("Found %1) %2 : %3", i, enumId, name)); i++; } // the permissions needs to be reverted back to original condition. CodeAccessPermission::revertAssert(); } }
Do not forget to build your models and full DB sync after!
Wednesday, September 2, 2020
GUID based Data contract fields are not available for SysOperationAutomaticUIBuilder
In order to play with Data Contract fields before or after running dialog in terms of SysOperation framework, we have to implement a SysOperationAutomaticUIBuilder based class.
And it works well, until you try to get access to GUID based fields via BindInfo() method: they are not allowed to be there!
Therefore, we get an exception in this case, alas.















