MSDyn365FO. Collation conflict during data upgrade from AX 2012

3-4.jpg

AX 2012 has well-known inconsistency in columns collation that has been reported before and now it is causing issues during data upgrade to latest version (8.0) of Dynamics 365 for Finance and Operations.

Data upgrade fails on step 9. postSync for data upgrade. From RELEASEUPDATESCRIPTSERRORLOG table you can get actual error: “[Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Cannot resolve the collation conflict between  “Latin1_General_CI_AS” and “SQL_Latin1_General_CP1_CI_AS” in the equal to operation.” It fails on the next x++ update statement in ReleaseUpdateDB80_TaxGTE_IN.updateTaxMeasureType() method:

update_recordset crosscompany taxMeasureType
    setting ClassNumber = sysModelElement.AxId
       where !taxMeasureType.ClassNumber
       join AxId from sysModelElement
       	where sysModelElement.ElementType == UtilElementType::Class
           && sysModelElement.Name == taxMeasureType.ClassName;

It was introduced in 8.0 update. Here it uses sysModelElement.Name and taxMeasureType.ClassName fields that have different collation.

Now we have several options to fix this:

  • Skip this method if you don’t use IN localization.
  • Convert source DB to D365 server collation.
  • Change collation of Name column in ModelElement table.

I already knew that we cannot simple use

ALTER DATABASE AxDB COLLATE SQL_Latin1_General_CP1_CI_AS;   <span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>

So, I looked at bacpac option proposed by Lane Swenka. We can export DB to a bacpac, edit collation there and import it again. But after I’ve got several thousand errors during the export I decided to switch to other options. I decided not to touch ModelElement because I hope that there is a reason behind its collation, so I went easiest rout for me – skip the code I don’t need.

I did not find nice and easy way to skip this method because everything is done via reflection and it’s hard to skip something using extension, but there is always a dirty way!

Quick extension forces D365 to run different method one more time that updates nothing instead of method I don’t want:

[ExtensionOf(classStr(SysDictClass))]
public final class SysDictClass_FUS_Extension
{
    public static anytype invokeObjectMethod(Object _object, IdentifierName _methodName, boolean _tryBaseClass)
    {
        if (_methodName == "updateTaxMeasureType")
        {
            _methodName = "updateTaxDocumentExtensionIN";
        }

        return next invokeObjectMethod(_object, _methodName, _tryBaseClass);
    }
}

That’s all. After this small hack update goes nice and smoothly.

DontTryTHisAtHome

Solution proposed here is not a real solution, but a hack. It is ok for me because I’m 100% sure that I don’t need this data to be upgraded. I would strongly recommend converting DB to SQL server collation or raise this with MS in case you hit this to get real solution that would be supported.

Happy hacking!

Advertisements

D365FO. “The authentication process was not successful. Please contact your system administrator.” error during Connect to Lifecycle Services setup.

As we know, there are hundreds of task recordings available out of the box in LCS library. Before first use, system administrator should authorize D365FO to access LCS and often it is not possible due to the old bug. When system administrator goes to Settings -> Task recorder -> Play recording as guide -> Open from Lifecycle Services and clicks “Select the Lifecycle Services library” “Connect to Lifecycle Services” dialog pops up.

Connect to Lifecycle Services

There is a link “Click here to connect to Lifecycle Services” and when administrator clicks on it he may receive “The authentication process was not successful. Please contact your system administrator.” error.

It’s is caused by empty value of “LCS.GettingStartedLibrary” parameter in web.config file.

To fix it go to K:\AosService\WebRoot and edit web.config file. Find LCS.GettingStartedLibrary tag and set value to any integer that is greater than 0, save file and restart IIS. Don’t forget to backup web.config before doing any changes.

WebConfigLCSGettingStarted

Probably LCS should put BPM library id there, however, it works with any random positive integer as well. To find BMP project id, go to LCS, open your project and click on “Business process modeler” tile. Then select library that contains task recording.  In the URL of the page grab value of FWK parameter

LCSBPMURL.png

Hopefully, this bug will be fixed soon and we won’t have to play with web.config file anymore.

D365FOE. How to override form data source field lookup method.

A long time ago, I wrote a blog post explaining how to override form data source field methods. I skipped lookup, most popular method, because it did not work and logged a bug with MS hoping that it would be fixed soon. It took more time than I expected, but finally, it works!

Here is a small example how to override it. I’m going to use approach similar to previous post, full example is available on GitHub. Version I’m using is 7.3 PU13.

/// <summary>
/// Handles events raised by <c>SalesTable</c> form.
/// </summary>
public class SalesTableEventHandler
{
    /// <summary>
    /// Post event handler for <c>SalesTable</c> <c>SalesLine</c> Initialized event.
    /// </summary>
    /// <param name=“_sender”></param>
    /// <param name=“_e”></param>
    [FormDataSourceEventHandler(formDataSourceStr(SalesTable, SalesLine), FormDataSourceEventType::Initialized)]
    public static void SalesLine_OnInitialized(FormDataSource _sender, FormDataSourceEventArgs _e)
    {
        var overrides = SalesTableFormExtensionOverrides::construct();

        _sender.object(fieldNum(SalesLine, ItemId)).registerOverrideMethod(methodStr(FormDataObject, lookup),
            methodStr(SalesTableFormExtensionOverrides, ItemId_OnLookup), overrides);
    }
}

/// <summary>
/// Contains methods which are used to override <c>SalesLine</c> data source field methods.
/// </summary>
public class SalesTableFormExtensionOverrides
{
    protected void new()
    {
    }

    /// <summary>
    /// Constructs a new instance of <c>SalesTableFormExtensionOverrides</c> class.
    /// </summary>
    /// <returns>
    /// A <c>SalesTableFormExtensionOverrides</c> class.
    /// </returns>
    public static SalesTableFormExtensionOverrides construct()
    {
        return new SalesTableFormExtensionOverrides();
    }

    /// <summary>
    /// Provides a lookup for the <c>InventTable</c> table
    /// </summary>
    /// <param name = "_callingControl">
    /// The form string control object with which to perform the lookup.
    /// </param>
    public void ItemId_OnLookup(FormStringControl _callingControl)
    {
        SysTableLookup sysTableLookup = SysTableLookup::newParameters(tableNum(InventTable), _callingControl);

        sysTableLookup.addLookupfield(fieldNum(InventTable, ItemId));
        sysTableLookup.addLookupfield(fieldNum(InventTable, NameAlias));

        sysTableLookup.performFormLookup();
    }
}

As you may notice, signature of this method is different to signature of other methods, like validate or jumpRef. They accept FormDataObject as a parameter and lookup() accepts FormStringControl. It looks a bit inconsistent for me and we need to be extra careful here because you can register override method with any signature and get error in the run-time.

D365FOE. Form personalization for developers.

Blog_image_dashboard.jpg

Often in AX one form could be used for different business entities, like customer and vendor external items. And because they use same CustVendExternalItem form under the hood personalization of it affects all entities in the same time. But what if user wants to add new control via personalization only for customer’s and does not want to see it for vendor’s external items?  Now it is possible with a small customization.

In D365 new FormRunConfigurationClass class has been introduced to control form personalization. It saves and restores all changes done by user: new controls added via personalization, columns resize, columns hide, etc. Each form has new property – configurationOwner() that is used by FormRunConfigurationClass to resolve configuration. By default, it is equal to a form name.

To split customer external items and vendor external items form personalization all we need to do is to set configurationOwner()  to overAllModule in the init() method of CustVendExternalItem  form, that is equal to ModuleInventCustVend::Cust for customer and to ModuleInventCustVend::Vend for vendor:

[ExtensionOf(formStr(CustVendExternalItem))]
public final class CustVendExternalItemForm_Extension
{
    public void init()
    {
        //copy-pasted this code from init method because it resolves module after super() and we need it before
        switch (this.args().parmEnum())
        {
            case ModuleInventPurchSalesVendCustGroup::Purch,
                 ModuleInventPurchSalesVendCustGroup::Vend,
                 ModuleInventPurchSalesVendCustGroup::VendGroup:
                 // set personalization owner
                 this.setConfigurationOwner(enum2str(ModuleInventCustVend::Vend));
                break;

            case ModuleInventPurchSalesVendCustGroup::Sales,
                 ModuleInventPurchSalesVendCustGroup::Cust,
                 ModuleInventPurchSalesVendCustGroup::CustGroup :
                 // set personalization owner
                 this.setConfigurationOwner(enum2str(ModuleInventCustVend::Cust));
                break;
        }

        next init();
    }
}

This property should be set before super() of init() method of the form, so if you have controls added in the run-time it won’t affect them. However, FormRunConfigurationClass can load saved personalization for this control as well. Let’s expand our example with new run-time control and load personalization for it:

[ExtensionOf(formStr(CustVendExternalItem))]
public final class CustVendExternalItemForm_Extension
{
    public void init()
    {
        //copy-pasted this code from init method because it resolves module after super() and we need it before
        switch (this.args().parmEnum())
        {
            case ModuleInventPurchSalesVendCustGroup::Purch,
                ModuleInventPurchSalesVendCustGroup::Vend,
                 ModuleInventPurchSalesVendCustGroup::VendGroup:
                this.setConfigurationOwner(enum2str(ModuleInventCustVend::Vend));
                break;

            case ModuleInventPurchSalesVendCustGroup::Sales,
                ModuleInventPurchSalesVendCustGroup::Cust,
                 ModuleInventPurchSalesVendCustGroup::CustGroup :
                this.setConfigurationOwner(enum2str(ModuleInventCustVend::Cust));
                break;
        }

        next init();

        //add runtime control
        FormGridControl gridControl = this.design().controlName(formControlStr(CustVendExternalItem, Grid));
        FormStringControl newControl = gridControl.addControl(FormControlType::String, 'NewControl');
        newControl.label('New control');

        //load personalization for runtime control
 this.configurationHelper().getLoadedConfigurationProfile().applyRuntimePropertiesForControl(this, gridControl, newControl);
    }
}

That’s only two examples of FormRunConfigurationClass usage, but I’m sure that my curious reader will find more.

Advanced cross-reference search.

Cross-references search

Cross-reference is one of the best tools we have in AX that is used by developers daily, however, not everyone is using it for 100%.

Recently, I was asked how to find cross-references for a kernel method that is not defined on a table, because you cannot right click on it 😊   And that’s a good question, often we want to find if there is a call to doUpdate or doInsert somewhere.

In AX 2012 go to Tools -> Cross-reference -> Names and filter by table or method name and then click Used by.

SalesLine.DoUpdate_Names

SalesLine.DoUpdate_UsedBy

Using this form, we can find usage of CRL types as well, for example, Microsoft.Dynamics.IntegrationFramework.  It is defined in AOT under references node, however, from there you cannot find any references.  Names would show you everything!

CLRTypeUsage

What about D365FOE?

As we know, it’s a bit different here. Cross-references data is moved to DYNAMICSXREFDB database.

DYNAMICSXREFDB

And it makes perfect sense because you need to pay for each GB of DB space in Production.

Using next SQL statement, we can find usage of any object. For example, Microsoft.Dynamics.IntegrationFramework :

SELECT
	sourceName.[Path] as sourcePath,
	targetName.[Path] as targetPath,
	[Kind],
	[Line],
	[Column],
	sourceModule.[Module] as sourceModule,
	targetModule.[Module] as targetModule
	FROM dbo.[References]
	INNER JOIN dbo.[Names] sourceName
		ON dbo.[References].[SourceId] = sourceName.[Id]
	INNER JOIN dbo.[Modules] sourceModule
		ON sourceName.[ModuleId] = sourceModule.[Id]
	INNER JOIN dbo.[Names] targetName
		ON dbo.[References].[TargetId] = targetName.[Id]
	INNER JOIN dbo.[Modules] targetModule
		ON targetName.[ModuleId] = targetModule.[Id]
	WHERE targetName.[Path] like '%Microsoft.Dynamics.IntegrationFramework%'

CLRTypeUsage_D365FOE

Where Kind is:

    /// <summary>
    /// Types of Cross References
    /// </summary>
    public enum CrossReferenceKind
    {
        /// <summary>
        /// Type not specified. Used for queries
        /// </summary>
        Any = 0,

        /// <summary>
        /// Indicates that the reference is a Method Call
        /// </summary>
        MethodCall = 1,

        /// <summary>
        /// Type reference
        /// Indicated that the type is used (variable and field declaration, attributes, function return type, etc)
        /// </summary>
        TypeReference = 2,

        /// <summary>
        /// Interface implementation
        /// Indicates that the source entity is implementing this interface
        /// </summary>
        InterfaceImplementation = 3,

        /// <summary>
        /// Class Extended
        /// Indicates that the source entity is extending this class or interface
        /// </summary>
        ClassExtended = 4,

        /// <summary>
        /// Test Call
        /// Indicates that the source entity (test) directly or indirectly calls an application method.
        /// </summary>
        TestCall = 5,

        /// <summary>
        /// Property
        /// Indicates that the source entity has a certain property.
        /// </summary>
        Property = 6,

        /// <summary>
        /// Attribute reference
        /// Indicated that an Attribute is used
        /// </summary>
        Attribute = 7,

        /// <summary>
        /// Test Helper Call
        /// Indicates that the source entity is a test helper.
        /// </summary>
        TestHelperCall = 8,

        /// <summary>
        /// Metadata or code Tag reference
        /// Indicates that the source tag is used on a metadata element, class or a method or a line of code.
        /// </summary>
        Tag = 9,
    }

Let’s try doUpdate:

DoUpdate_UsedBy_D365FOE

As we can see, result is different to AX 2012, where we could search for an individual table, now all Common methods have reference to Common.

 

D365FOE. FormHasMethod extension for form extension methods.

Recently I have seen multiple people asking how to check if form has method added by extension at run-time.  For form methods we can use Global::formHasMethod but it does not work with form extensions. I advised people to use new metadata API to do this and finally community user Axaptus wrote the code!

I tweaked it a bit to ignore method’s name case as AX does and to exclude private methods. Also I used it to extend standard formHasMethod method

/// <summary>
/// The class <c>Global_Extension</c> contains extension methods for the <c>Global</c> class.
/// </summary>
[ExtensionOf(classStr(Global))]
public static final class Global_Extension
{
    static boolean formHasMethod(FormRun fr, IdentifierName methodName)
    {
        boolean ret = next formHasMethod(fr, methodName);

        if (!ret)
        {
            ret = Global::formExtensionHasMethod_IM(fr, methodName);
        }

        return ret;
    }

    private static boolean formExtensionHasMethod_IM(FormRun _formRun, IdentifierName _methodName)
    {
        if (!_formRun || !_methodName)
        {
            return false;
        }

        try
        {
            System.Object[] extensions = Microsoft.Dynamics.Ax.Xpp.ExtensionClassSupport::GetExtensionsOnType(_formRun.GetType(), true);

            if (extensions)
            {
                System.Type    formRunExtensionType;
                System.Reflection.MethodInfo    methodInfo;
                
                //extension methods are always static
                var  bindingFlags = BindingFlags::Public | BindingFlags::Static | BindingFlags::IgnoreCase;

                for (int i = 0; i < extensions.Length; i++)
                {
                    formRunExtensionType = extensions.GetValue(i);

                    var methodsInfo = formRunExtensionType.GetMethods(bindingFlags);

                    for (int n = 0; n < methodsInfo.get_Length(); n++)
                    {
                        methodInfo = methodsInfo.getValue(n);
                        if (methodInfo.Name == _methodName)
                        {
                            return true;
                        }
                    }
                }
            }
        }
        catch (Exception::CLRError)
        {
            error(CLRInterop::getLastException().ToString());
        }

        return false;
    }
}

Extending standard method has its pros and cons. From one side it will slow down execution of standard code that calls it when method does not exist, but it’s a rare case. From another side it allows you to reuse standard code without changing it and it could be handy in various places where AX looks for a method on a form.

Source code is available on GitHub