Wednesday, June 23, 2021
Monday, June 21, 2021
TF command line to fix the error: Mapping on the working folder is already in use
Just to document my command line to get rid of this blocking error.
tf workspace /delete DEV365-FO-VM-3;"Name Surname" /collection:https://YOURCOMPANYPROJECT.visualstudio.com
My first D365FO Build pipeline with Microsoft-hosted agent
This contains some particular details and explanatory images which can be useful while following the basic Microsoft article https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/hosted-build-automation
Thanks Joris for the NuGet packages and other colleagues for their help.
So, my goal is to create a Build pipeline for Version 18, and my deployable package must have a few ISV models, one of which is provided as libraries and source code.
Personal Access Token
First create or update your Personal Access Token and copy-paste it in a secure place (I mean Notepad++, of course). This will be used as a password for uploading Nuget packages to your Artifacts feed later.
Create a feed
NuGet packages
Get NuGet packages from LCS shared asset library.
Place these files in a special NuGet folder and create or update packages.config file.
Publish packages by using the command line
Creating the pipeline
/p:ReferencePath A semicolon-separated list of paths that contain any non-X++ binaries that are referenced and required for compilation. You should include the location of the extracted Compiler Tools NuGet package, because it might contain required references.
So, once triggered it builds the solution, creates a deployable deployable package and publishes it.
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.





















