среда, 29 ноября 2023 г.

Open D365F&O form with specific query parameters in URL

Recently I was struggled with one task assigned to me. I had to store string as URL that points to the particular form with particular query filtered. For instance, open particular sales order or some voucher. A colleguae of mine has proposed coming up with such a logic to generate deep link with all required parameters:
       
using Microsoft.Dynamics.AX.Framework.Utilities;
using Microsoft.Dynamics.ApplicationPlatform.Environment;

class M_DeepLinkCreator
{
     /*#######################################################################################################################################*/
    public static str createDeepLink(str _menuItemName, DataSourceName _dataSourceName, Map _fieldValuesMap, DataAreaId _dataAreaId = curExt())
    {
        IApplicationEnvironment env         = EnvironmentFactory::GetApplicationEnvironment();
        System.Uri              host        = new System.Uri(env.Infrastructure.HostUrl);
        MapEnumerator           mapEnumerator;
        UrlHelper.UrlGenerator  generator   = new UrlHelper.UrlGenerator();

        generator.HostUrl       = host.GetLeftPart(System.UriPartial::Path);
        generator.Company       = _dataAreaId;
        generator.MenuItemName  = _menuItemName;

        if (_dataSourceName && _fieldValuesMap)
        {
            mapEnumerator = _fieldValuesMap.getEnumerator();

            var requestQueryParameterCollection = generator.RequestQueryParameterCollection;

            while (mapEnumerator.moveNext())
            {
                requestQueryParameterCollection.UpdateOrAddEntry(_dataSourceName, mapEnumerator.currentKey(), mapEnumerator.currentValue());
            }
        }

        return generator.GenerateFullUrl().AbsoluteUri;
    }
    /*#######################################################################################################################################*/

}    
 
Hope it's going to be useful for you in your development process.

среда, 11 октября 2023 г.

Dynamics 365F&O different types of functions and classess

Just like a small reminder for those who might be forgetting it from time to time. Me personally find this piece of information pretty useful as a remark :-)

Functions:

Static Functions: These functions are tied to the class rather than an instance. They can be called without creating an instance of the class. They're commonly used for utility functions that don't rely on instance-specific data.

Instance Functions: These functions require an instance of the class to be invoked. They operate on the data that belongs to the object and are responsible for object-specific behavior.

Main Method: This is the entry point for class execution, usually for testing or batch processing. The main() method is static and can accept command-line arguments.

Final Functions: These are functions that cannot be overridden in derived classes. This ensures that the implementation of the function remains consistent.

Abstract Functions: These functions don't have any implementation in the base class. Derived classes must provide an implementation for these functions, making them ideal for defining a common interface.

Classes:
Table Classes: These classes directly represent tables in the AOT (Application Object Tree). They're automatically created and can be extended but not modified.

Form Classes: These are auto-generated when you create a form in the AOT. They contain methods that run form logic and control form events.

Data Provider Classes: Used primarily for SSRS reports, these classes gather the data that is then displayed on the report.

Framework Classes: These classes provide foundational structures for common functionalities. Classes like RunBase and RunBaseBatch are examples that provide a standardized way to create batch jobs or runnable classes.

Helper Classes: These are custom-defined classes that encapsulate shared logic or functionalities that can be reused across modules.

Controller Classes: These classes act as mediators in complex operations like reporting or batch processing, organizing the overall execution flow.

Contract Classes: These are used to encapsulate parameters for services or reports, making it easier to manage and validate the input.

Extension Classes: These allow you to add new methods to existing table, form, or class objects without altering the original codebase.

Attribute Classes: These are special classes that act as metadata, allowing you to tag elements in the code for additional behaviors or properties.

Map Classes: These simulate tables but don't involve data storage in the database. They're useful for temporary data manipulation tasks.


Special Classes:
Global Class: This class contains global methods and variables that can be accessed across the application, serving as a utility hub.

Application Classes: Classes like Info, ClassFactory, and Global that serve specific application-level functionalities.

Sys Classes: These are system-level classes such as SysDictTable, SysQuery, and SysFormRun. They are crucial for interacting with system-level functionalities and metadata.

понедельник, 19 июня 2023 г.

Convert set to container X++

Recently I've faced with a task - I had two sets and I need to check item by item for both of them to identify number of common values. Going with Enumerator or Iterator would be the obvious way. However, I wanted to eliminate of ambiguous code and found different way. I converted SET to Container and checked elements one by one. Maybe it's not the best solution from performance standpoint (yes, I'm aware of slowness of containers), but it's the simplest one. So, the actual conversion of the Set to Container I did via next code:

Set incomeSet = new Set(Types::String);
cntainer outputCon;
//filling in set.
outputCon = condel(incomeSet.pack(), 1,3);
The
condel()
funciton deletes all special symbols and other staff and in the end you'll get something like that:

вторник, 31 августа 2021 г.

Get the Enum name or value via SQL for D365F&O

Hello. Today I'm gonna show you how you can get the enum value based on the enum name from D365F&O. Frequently, it's quite hard to find proper enum value for some base enum, for example, InventTransType. And what if you know only the system name, but not the Id or Label and you wanted to find it as quickly as possible? Below you may find the SQL script to get all you need at once:
select t1.*, t2.* from ENUMIDTABLE t1 inner join ENUMVALUETABLE t2 on t1.ID=t2.ENUMID where t1.NAME='InventTransType'
And here what you've got
I hope that information will be useful for you. Happy DAXing!

пятница, 2 октября 2020 г.

Get size of all tables in current database SQL

 I have a task - to perform data upgrade from AX2012 to D365FO and I was wondering how big is each of the customer's table. I'm aware of Object explorer details(F7) in SQL Management studio but it works not fast as I'd like to. So I googled and found a bunch of different scripts that shows different information regarding your database.

One of that scripts I used and the result was pretty good. So I recommend you to use that script if you'd have the same task. The code is below:

SELECT a2.name AS TableName, a1.rows as [RowCount], --(a1.reserved + ISNULL(a4.reserved,0)) * 8 AS ReservedSize_KB, --a1.data * 8 AS DataSize_KB, --(CASE WHEN (a1.used + ISNULL(a4.used,0)) > a1.data THEN (a1.used + ISNULL(a4.used,0)) - a1.data ELSE 0 END) * 8 AS IndexSize_KB, --(CASE WHEN (a1.reserved + ISNULL(a4.reserved,0)) > a1.used THEN (a1.reserved + ISNULL(a4.reserved,0)) - a1.used ELSE 0 END) * 8 AS UnusedSize_KB, CAST(ROUND(((a1.reserved + ISNULL(a4.reserved,0)) * 8) / 1024.00, 2) AS NUMERIC(36, 2)) AS ReservedSize_MB, CAST(ROUND(a1.data * 8 / 1024.00, 2) AS NUMERIC(36, 2)) AS DataSize_MB, CAST(ROUND((CASE WHEN (a1.used + ISNULL(a4.used,0)) > a1.data THEN (a1.used + ISNULL(a4.used,0)) - a1.data ELSE 0 END) * 8 / 1024.00, 2) AS NUMERIC(36, 2)) AS IndexSize_MB, CAST(ROUND((CASE WHEN (a1.reserved + ISNULL(a4.reserved,0)) > a1.used THEN (a1.reserved + ISNULL(a4.reserved,0)) - a1.used ELSE 0 END) * 8 / 1024.00, 2) AS NUMERIC(36, 2)) AS UnusedSize_MB, --'| |' Separator_MB_GB, CAST(ROUND(((a1.reserved + ISNULL(a4.reserved,0)) * 8) / 1024.00 / 1024.00, 2) AS NUMERIC(36, 2)) AS ReservedSize_GB, CAST(ROUND(a1.data * 8 / 1024.00 / 1024.00, 2) AS NUMERIC(36, 2)) AS DataSize_GB, CAST(ROUND((CASE WHEN (a1.used + ISNULL(a4.used,0)) > a1.data THEN (a1.used + ISNULL(a4.used,0)) - a1.data ELSE 0 END) * 8 / 1024.00 / 1024.00, 2) AS NUMERIC(36, 2)) AS IndexSize_GB, CAST(ROUND((CASE WHEN (a1.reserved + ISNULL(a4.reserved,0)) > a1.used THEN (a1.reserved + ISNULL(a4.reserved,0)) - a1.used ELSE 0 END) * 8 / 1024.00 / 1024.00, 2) AS NUMERIC(36, 2)) AS UnusedSize_GB FROM (SELECT ps.object_id, SUM (CASE WHEN (ps.index_id < 2) THEN row_count ELSE 0 END) AS [rows], SUM (ps.reserved_page_count) AS reserved, SUM (CASE WHEN (ps.index_id < 2) THEN (ps.in_row_data_page_count + ps.lob_used_page_count + ps.row_overflow_used_page_count) ELSE (ps.lob_used_page_count + ps.row_overflow_used_page_count) END ) AS data, SUM (ps.used_page_count) AS used FROM sys.dm_db_partition_stats ps --===Remove the following comment for SQL Server 2014+ --WHERE ps.object_id NOT IN (SELECT object_id FROM sys.tables WHERE is_memory_optimized = 1) GROUP BY ps.object_id) AS a1 LEFT OUTER JOIN (SELECT it.parent_id, SUM(ps.reserved_page_count) AS reserved, SUM(ps.used_page_count) AS used FROM sys.dm_db_partition_stats ps INNER JOIN sys.internal_tables it ON (it.object_id = ps.object_id) WHERE it.internal_type IN (202,204) GROUP BY it.parent_id) AS a4 ON (a4.parent_id = a1.object_id) INNER JOIN sys.all_objects a2 ON ( a1.object_id = a2.object_id ) INNER JOIN sys.schemas a3 ON (a2.schema_id = a3.schema_id) WHERE a2.type <> N'S' and a2.type <> N'IT' --AND a2.name = 'MyTable' --Filter for specific table --ORDER BY a3.name, a2.name ORDER BY ReservedSize_MB DESC

Just do the Copy-> Paste into your management studio and run in in front of the desirable database.

вторник, 30 июля 2019 г.

How to filter record by dimension values in D365


Hi all,

It's been a long time I wrote the post in my blog. But today I will fresh it by writing the new one.

I got a task - to filter the query on worker's default dimension fields. This was quite challenging for me.
First I did it via DimensionAttributeValueSetStorage class.  It didn't feet the requirement because the functionality has to have a possibility to filter records via any of the dimensions (Division, Location, CostCenter, Department, etc).
Next, I found an article about DimensionsProvider class which has some sort of abilities that I was needed. And I used it.

It worked as expected and as required!

So, below I provided a piece of code which you can interpret for your requirements, but I believe the general concept will be clear:

private void filterResourcesByDimensions(Query _q)
    {
        Counter                             i;
        DimensionAttribute                  dimensionAttribute;
        str                                 dimValue;
        container                           workerDefaultDimension, workerDefaultDimensionVal;
        QueryBuildDataSource                qbdsResource;
        
        DimensionProvider                   dimProvider = new DimensionProvider();

        workerDefaultDimension = ['Division', 'Location', 'Region', 'ServiceLine', 'SubService'];

        workerDefaultDimensionVal = [_context.division(),
                                    _context.location(),
                                    _context.region(),
                                    _context.serviceLine(),
                                    _context.subService()
                                    ]; //Dimensions values (any)

        qbdsResource = _q.dataSourceTable(tableNum(ResCompanyResourceView));
   
        for (i = 1; i <= conLen(workerDefaultDimension); i++)
        {
            dimensionAttribute = dimensionAttribute::findByName(conPeek(workerDefaultDimension,i));
       
            if (dimensionAttribute.RecId == 0 && conpeek(workerDefaultDimensionVal, i) == '')
            {
                continue;
            }
       
            dimValue = conPeek(workerDefaultDimensionVal,i);
       
            if (dimValue != "")
            {
                dimProvider.addAttributeRangeToQuery(_q, qbdsResource.name(), identifierStr(DefaultDimension), DimensionComponent::DimensionAttribute, dimValue, dimensionAttribute.Name);
            }
        }
    }

Feel free to contact if you still have any questions! I'm