Let’s pass MB-820 – Episode 1: Install and Upgrade codeunits

Share This Post

Today I’m starting a new blog and video series dedicated to the new certification exam for AL developers: MB-820.

The main topic of this blog is the Install and Upgrade subtype codeunits. Though, I will touch on DataTransfer data type and upgrading apps in BC just because the Microsoft lesson for MB-820 includes them:

Prepare for an easy application upgrade experience in Business Central – Training | Microsoft Learn

I have covered Install and Upgrade codeunits here for Dynamics NAV, but we are in a different world now and in this blog will look at current trends in upgrade and install codeunit by peaking at how Microsoft does it in AL and Business Central and then trying to replicate it in a sample app.

Let’s have a look at how Microsoft manages data upgrades in codeunit 104000 of Base Application extension:

    trigger OnUpgradePerCompany()
    begin
        if not HybridDeployment.VerifyCanStartUpgrade(CompanyName()) then
            exit;

        ClearTemporaryTables();

        UpdateGenJournalBatchReferencedIds();
        UpdateJobs();
        UpdateItemTrackingCodes();
        UpgradeJobQueueEntries();
        UpgradeNotificationEntries();
        UpgradeVATReportSetup();
        UpgradeStandardCustomerSalesCodes();
        UpgradeStandardVendorPurchaseCode();
        MoveLastUpdateInvoiceEntryNoValue();
        CopyIncomingDocumentURLsIntoOneFiled();
...

And if we were to analyze one of the upgrade methods, we will notice 3 parts:

    local procedure UpdateGenJournalBatchReferencedIds()
    var
        GenJournalBatch: Record "Gen. Journal Batch";
        UpgradeTag: Codeunit "Upgrade Tag";
        UpgradeTagDefinitions: Codeunit "Upgrade Tag Definitions";
    begin
       //part 1
       // check if the current upgrade tag has been executed already
    if UpgradeTag.HasUpgradeTag(UpgradeTagDefinitions.GetBalAccountNoOnJournalAPIUpgradeTag()) then exit;

        
       //part 2
       // execute current update
if GenJournalBatch.FindSet() then
            repeat
                GenJournalBatch.UpdateBalAccountId();
                if GenJournalBatch.Modify() then;
            until GenJournalBatch.Next() = 0;

       //part 3       
       //record current upgrade tag
UpgradeTag.SetUpgradeTag(UpgradeTagDefinitions.GetBalAccountNoOnJournalAPIUpgradeTag());
    end;

The tag is defined in codeunit 9998 Upgrade Tag Definition:

    internal procedure GetBalAccountNoOnJournalAPIUpgradeTag(): Code[250]
    begin
        exit('MS-275328-BalAccountNoOnJournalAPI-20180823');
    end;

The idea is to record all tags as they get applied in each company so that if a new company is created, all previous updates could be executed while executed upgrades are skipped if they were already executed.

The upgrade tags are stored in the Upgrade Tags table as seen below:

Let’s look at the following use case. My extension will introduce a new setup table:

table 50135 "Custom Setup"
{
    DataClassification = ToBeClassified;

    fields
    {
        field(1; PK; Code[1])
        {
            DataClassification = ToBeClassified;

        }
        field(2; "No. of Retries"; Integer)
        {
            DataClassification = ToBeClassified;
        }
        field(3; "Default URL"; Code[50])
        {
            DataClassification = ToBeClassified;
        }
        field(4; "Type of app"; enum "App Type")
        {
            DataClassification = ToBeClassified;
        }
    }

    keys
    {
        key(Key1; PK)
        {
            Clustered = true;
        }
    }
}

As a good ISV that we are we want to initialize the Custom Setup table with default values.

This can be done in an Install subtype codeunit like or close to what’s below:

codeunit 50135 InstallCU
{
    Subtype = Install;

    trigger OnInstallAppPerCompany()
    var
        ModuleInfo: ModuleInfo;
    begin
        NavApp.GetCurrentModuleInfo(ModuleInfo);
        if ModuleInfo.DataVersion() = Version.Create(0, 0, 0, 0) then //new installation
            InstallPerCompany()
        else
            //reinstallation
            case true of
                ModuleInfo.DataVersion() = Version.Create(1, 0, 0, 6):
                    //add reinstallation code for each version
                    ReInstallPerCompany();
            end;
    end;
    trigger OnInstallAppPerDatabase()
    var
    begin
    end;
    local procedure InstallPerCompany()
    var
        CustomSetup: Record "Custom Setup";
    begin
        CustomSetup.PK := '';
        CustomSetup."Default URL" := 'https://www.google.com';
        CustomSetup."No. of Retries" := 300;
        CustomSetup."Type of app" := CustomSetup."Type of app"::"ASP.NET MVC";
        CustomSetup.Insert();
    end;
    local procedure ReInstallPerCompany()
    var
        CustomSetup: Record "Custom Setup";
    begin
        CustomSetup.Init();
        CustomSetup.PK := '';
        CustomSetup."Default URL" := 'https://www.google.com';
        CustomSetup."No. of Retries" := 300;
        CustomSetup."Type of app" := CustomSetup."Type of app"::"ASP.NET MVC";
        if not CustomSetup.Insert() then 
            CustomSetup.Modify();
    end;
}

Overtime, we can change data types, or values in our setup tables.

For example, I changed an enum and an integer default value.

Initially:

enum 50135 "App Type"
{
    Extensible = true;

    value(0; "ASP.NET Web App")
    {
    }
    value(1; "ASP.NET MVC")
    {
    }
}

While now:

enum 50135 "App Type"
{
Extensible = true;

value(0; "-NA")
{
}
value(1; "ASP.NET Web App")
{
}
value(2; "ASP.NET MVC")
{
}
value(3; "Ruby on Rails")
{
}
}

With each change introduced in a specific version we can add a new upgrade function just like below and just like in Base Application Upgrade codeunit:

codeunit 50136 UpgradeCU
{
    // 1. Debug without publishing (use attach config)
    // 2. Publish without debugging
    Subtype = Upgrade;

    trigger OnUpgradePerCompany()
    begin
        // Run upgrade code
        UpgradeCustomSetup(); //upgrade Tag 1
        UpgradeCustomSetupAgain();//upgrade Tag 2
    end;

    local procedure UpgradeCustomSetup()
    var
        CustomSetup: Record "Custom Setup";
        ModuleInfo: ModuleInfo;
        CustomCU: Codeunit "Custom CU";
        UpgradeTagMgt: Codeunit "Upgrade Tag";
    begin
        // Check whether the tag has been used before, and if so, don't run upgrade code
        if UpgradeTagMgt.HasUpgradeTag(CustomCU.GetCustomSetupTag()) then
            exit;

        if not NavApp.GetCurrentModuleInfo(ModuleInfo) then
            Clear(ModuleInfo);

        case true of
            ModuleInfo.DataVersion() = Version.Create(1, 0, 0, 4):
                begin
                    if CustomSetup.FindFirst() then begin
                        CustomSetup."Default URL" := 'https://www.google.ca';
                        CustomSetup."No. of Retries" := 300;
                        CustomSetup."Type of app" := CustomSetup."Type of app"::"-NA";
                        CustomSetup.Modify();
                    end;
                end;
            ModuleInfo.DataVersion() = Version.Create(1, 0, 0, 5):
                begin
                    if CustomSetup.FindFirst() then begin
                        CustomSetup."Default URL" := 'https://www.google.jp';
                        CustomSetup."No. of Retries" := 300;
                        CustomSetup."Type of app" := CustomSetup."Type of app"::"Ruby on Rails";
                        CustomSetup.Modify();
                    end;
                end;
        end;

        // Insert the upgrade tag in table 9999 "Upgrade Tags" for future reference
        UpgradeTagMgt.SetUpgradeTag(CustomCU.GetCustomSetupTag());
    end;

    local procedure UpgradeCustomSetupAgain()
    var
        CustomSetup: Record "Custom Setup";
        ModuleInfo: ModuleInfo;
        CustomCU: Codeunit "Custom CU";
        UpgradeTagMgt: Codeunit "Upgrade Tag";
    begin
        // Check whether the tag has been used before, and if so, don't run upgrade code
        if UpgradeTagMgt.HasUpgradeTag(CustomCU.GetCustomSetupTag2()) then
            exit;

        if not NavApp.GetCurrentModuleInfo(ModuleInfo) then
            Clear(ModuleInfo);

        case true of
            ModuleInfo.DataVersion() = Version.Create(1, 0, 0, 4):
                begin
                    if CustomSetup.FindFirst() then begin
                        CustomSetup."Default URL" := 'https://www.google.ca';
                        CustomSetup."No. of Retries" := 300;
                        CustomSetup."Type of app" := CustomSetup."Type of app"::"-NA";
                        CustomSetup.Modify();
                    end;
                end;
            ModuleInfo.DataVersion() = Version.Create(1, 0, 0, 5):
                begin
                    if CustomSetup.FindFirst() then begin
                        CustomSetup."Default URL" := 'https://www.google.se';
                        CustomSetup."No. of Retries" := 100;
                        CustomSetup."Type of app" := CustomSetup."Type of app"::"-NA";
                        CustomSetup.Modify();
                    end;
                end;
        end;
        // Insert the upgrade tag in table 9999 "Upgrade Tags" for future reference
        UpgradeTagMgt.SetUpgradeTag(CustomCU.GetCustomSetupTag2());
    end;
}

Microsoft introduced DataTransfer data type to improve the performance of bulk copy operations during upgrades. It works on sets of data instead of working with individual records.

Check this Microsoft Learn page to learn more: Transferring data between tables using DataTransfer – Business Central | Microsoft Learn

Or, you could visit my previous blog where I translated the DataTransfer data type functionality into SQL statements:

Do you know how BC AL compiler talks to SQL Server? Hint: clues in the debugger … – Business Central Musings (svirlan.com)

Keep extensions up-to-date

Business Central is not a one block does it all, but rather an assembly of lego pieces put together that work to achieve the behavior the customer wanted and paid for.

Some of these pieces of lego belong to Microsoft, and Microsoft makes available upgrades periodically. The customer and their partner is responsible to apply these updates.

There are also pieces belonging to your partner or the ISVs that sold the customer a specific app.

These ISVs are performing upgrades to their own pieces as well and these new pieces have to be applied.

These new pieces can be applied in Admin Center, by clicking on the environment where we want to apply the upgrade, and clicking on Apps:

And for each app left behind we can enforce the updates:

Some apps have dependencies, and in this case you need to take care of upgrading dependencies first before you apply them.

And that is more or less the MB-820 Prepare for an easy application upgrade.

Prepare for an easy application upgrade experience in Business Central – Training | Microsoft Learn

Want to see a demo of install and upgrade codeunits?

Check my Youtube video and subscribe:

Good luck with your studies!

— Buy my book —

https://leanpub.com/BCLedgerEntriesInsights

Download pdf sample.

Share This Post

Related Articles

Leave a Reply

Recent Posts

Get Notified About New Posts

Categories

Discover more from Business Central Musings

Subscribe now to keep reading and get access to the full archive.

Continue reading