Dynamics 365 Business Central: Customizing an Integration with Microsoft Dataverse (Integrate custom tables)

Dynamics 365 Business Central

Hi, Readers.
Two weeks ago we talked about Dynamics 365 Sales and Business Central integration setup. And the following table lists the standard mapping between tables in Business Central and Dynamics 365 Sales that Business Central provides. (2021/08/12 BC18.3)

BC TableDirectionIntegration TableIntegration Field
ContactBidirectionalDataverse ContactContact
CurrencyToIntegrationTableDataverse TransactioncurrencyTransaction Currency
CustomerBidirectionalDataverse AccountAccount
Customer Price GroupToIntegrationTableCRM PricelevelPrice List
ItemBidirectionalCRM ProductProduct
OpportunityBidirectionalCRM OpportunityOpportunity
Payment TermsFromIntegrationTableDataverse AccountPayment Terms
Sales Invoice HeaderToIntegrationTableCRM InvoiceInvoice
Sales Invoice LineToIntegrationTableCRM InvoicedetailInvoice Product
ResourceBidirectionalCRM ProductProduct
Salesperson/PurchaserFromIntegrationTableDataverse SystemuserUser
Sales PriceToIntegrationTableCRM ProductpricelevelProduct Price List
Shipment MethodFromIntegrationTableDataverse AccountAddress 1: Freight Terms
Shipping AgentFromIntegrationTableDataverse AccountAddress 1: Shipping Method
Unit of MeasureToIntegrationTableCRM UomscheduleUnit Group
VendorBidirectionalDataverse AccountAccount

So, what if we want to add a new integration table?
This post will guide you through setting up an integration between a new table (Lab Book) in Business Central and a new entity (Lab Book) in Microsoft Dataverse.

Prerequisites:

1. Dynamics 365 Sales and Business Central integration setup must be completed first.
Dataverse Connection Setup:

Microsoft Dynamics 365 Connection Setup:

2. Create a new table you want to integrate with Dataverse in Business Central.
For example: table 50100 “Lab Book”

Source Code:

table 50100 "Lab Book"
{
    Caption = 'Lab Book';
    DataClassification = CustomerContent;

    fields
    {
        field(1; "No."; Code[10])
        {
            Caption = 'No.';
            DataClassification = CustomerContent;
        }
        field(2; Title; Text[30])
        {
            Caption = 'Title';
            DataClassification = CustomerContent;
        }
        field(3; Author; Text[30])
        {
            Caption = 'Author';
            DataClassification = CustomerContent;
        }
        field(4; Hardcover; Boolean)
        {
            Caption = 'Hardcover';
            DataClassification = CustomerContent;
        }
        field(5; "Page Count"; Integer)
        {
            Caption = 'Page Count';
            DataClassification = CustomerContent;
        }
    }

    keys
    {
        key(PK; "No.")
        {
            Clustered = true;
        }
    }
}

3. Create a new table in Dataverse.
table(entity): cr703_labbook

Okay, let’s get started.

PS: The Lab Book Card page source code. (The list page will be provided below)

page 50100 "Lab Book Card"
{
    Caption = 'Lab Book Card';
    PageType = Card;
    UsageCategory = Administration;
    SourceTable = "Lab Book";

    layout
    {
        area(Content)
        {
            group(General)
            {
                field("No."; Rec."No.")
                {
                    ApplicationArea = All;
                }
                field(Title; Rec.Title)
                {
                    ApplicationArea = All;
                }
            }

            group(Details)
            {
                field(Author; Rec.Author)
                {
                    ApplicationArea = All;
                }
                field(Hardcover; Rec.Hardcover)
                {
                    ApplicationArea = All;
                }
                field("Page Count"; Rec."Page Count")
                {
                    ApplicationArea = All;
                }
            }
        }
    }
}
Using AL Table Proxy Generator tool to create an integration table in Business Central for the Dataverse table

To integrate data from a Microsoft Dataverse table into Business Central, you must create a table object in Business Central that is based on the Microsoft Dataverse table, and then import the new table into the Business Central database.

The AL Table Proxy Generator tool can be used to generate one or more tables for integration with Microsoft Dataverse. When one or more tables are present in Microsoft Dataverse, but not in Dynamics 365 Business Central, the tool can be run to generate integration or proxy tables for the specified table or tables.

An integration or proxy table is a table that represents a table in Microsoft Dataverse. The integration table includes fields that correspond to columns in the Microsoft Dataverse table. The integration table acts as a link or connector between the Business Central table and the Microsoft Dataverse table.

The AL Table Proxy Generator tool is available with the AL Language extension. Look for the altpgen.exe tool in the equivalent folder of c:\users\<username>\.vscode\extensions\<al extension version>\bin. (Or C:\Users\yzhu\.vscode\extensions\ms-dynamics-smb.al-13.0.921843\bin\win32\altpgen)

For example: C:\Users\yzhu\.vscode\extensions\ms-dynamics-smb.al-7.4.496506\bin

1. Generating proxy tables

Start Windows PowerShell as an administrator.

From the command prompt, write .\altpgen.exe followed by the parameters as described below.

-Project
-PackageCachePath
-ServiceURI
-Entities
-BaseId
-[TableType]
ParameterDescription
ProjectThe AL project folder to create the table(s) in.
PackageCachePathThe AL project cache folder for symbols.
Note: It is important that the latest symbols have been downloaded because these are used for comparison when the tool runs.
ServiceURIThe server URL for Microsoft Dataverse. For example, https://tenant.crm.dynamics.com.
EntitiesThe table(s) to create in AL. If multiple, this must be specified as a comma-separated list.

Note: It is important that all related tables are specified too. Related tables are, for example, used for lookups and if the related tables are not found, a lookup will no longer be working. For more information, see the section Specifying tables.
BaseIdThe assigned starting ID for the generated new table(s) in AL.
TableTypeThe table type for the table(s) in AL. The options are CDS and CRM.

Note: If unspecified, the system looks both for CDS and CRM tables .

Open the folder where the altpgen.exe tool is.

cd C:\Users\yzhu\.vscode\extensions\ms-dynamics-smb.al-7.4.496506\bin

Or

cd "C:\Users\yzhu\.vscode\extensions\ms-dynamics-smb.al-13.0.921843\bin\win32\altpgen"

Then run the generate proxy tables command.
For example: Need to be case sensitive

.\altpgen -project:"C:\Users\yzhu\Documents\AL\IntegrationWithDataverse" -packagecachepath:"C:\Users\yzhu\Documents\AL\IntegrationWithDataverse\packagecachepath" -serviceuri:"https://org792fe27a.crm.dynamics.com/" -entities:cr703_labbook -baseid:50100 -tabletype:CDS

Update: Business Central 2024 wave 1 (BC24): Changes to AL Table Proxy Generator tool (altpgen) – Two additional arguments: ClientId and RedirectUri

Sign in with an administrator user account and give consent to the application that will be used to connect to Dataverse.

Complete: 1 files written.

Test Video:

Note:
The table can contain some or all of the fields from the Microsoft Dataverse table. However, if you want to set up bi-directional synchronization you must include all fields in the table.

A CDS table will be created automatically. ( table 50101 “CDS cr703_LabBook” : CDS + Entity Name)

The source code: For reference only, please do not create manually.

table 50101 "CDS cr703_LabBook"
{
  ExternalName = 'cr703_labbook';
  TableType = CDS;
  Description = '';

  fields
  {
    field(1;cr703_LabBookId;GUID)
    {
      ExternalName = 'cr703_labbookid';
      ExternalType = 'Uniqueidentifier';
      ExternalAccess = Insert;
      Description = 'Unique identifier for entity instances';
      Caption = 'Lab Book';
    }
    field(2;CreatedOn;Datetime)
    {
      ExternalName = 'createdon';
      ExternalType = 'DateTime';
      ExternalAccess = Read;
      Description = 'Date and time when the record was created.';
      Caption = 'Created On';
    }
    field(4;ModifiedOn;Datetime)
    {
      ExternalName = 'modifiedon';
      ExternalType = 'DateTime';
      ExternalAccess = Read;
      Description = 'Date and time when the record was modified.';
      Caption = 'Modified On';
    }
    field(24;statecode;Option)
    {
      ExternalName = 'statecode';
      ExternalType = 'State';
      ExternalAccess = Modify;
      Description = 'Status of the Lab Book';
      Caption = 'Status';
      InitValue = " ";
      OptionMembers = " ", Active, Inactive;
      OptionOrdinalValues = -1, 0, 1;
    }
    field(26;statuscode;Option)
    {
      ExternalName = 'statuscode';
      ExternalType = 'Status';
      Description = 'Reason for the status of the Lab Book';
      Caption = 'Status Reason';
      InitValue = " ";
      OptionMembers = " ", Active, Inactive;
      OptionOrdinalValues = -1, 1, 2;
    }
    field(28;VersionNumber;BigInteger)
    {
      ExternalName = 'versionnumber';
      ExternalType = 'BigInt';
      ExternalAccess = Read;
      Description = 'Version Number';
      Caption = 'Version Number';
    }
    field(29;ImportSequenceNumber;Integer)
    {
      ExternalName = 'importsequencenumber';
      ExternalType = 'Integer';
      ExternalAccess = Insert;
      Description = 'Sequence number of the import that created this record.';
      Caption = 'Import Sequence Number';
    }
    field(30;OverriddenCreatedOn;Date)
    {
      ExternalName = 'overriddencreatedon';
      ExternalType = 'DateTime';
      ExternalAccess = Insert;
      Description = 'Date and time that the record was migrated.';
      Caption = 'Record Created On';
    }
    field(31;TimeZoneRuleVersionNumber;Integer)
    {
      ExternalName = 'timezoneruleversionnumber';
      ExternalType = 'Integer';
      Description = 'For internal use only.';
      Caption = 'Time Zone Rule Version Number';
    }
    field(32;UTCConversionTimeZoneCode;Integer)
    {
      ExternalName = 'utcconversiontimezonecode';
      ExternalType = 'Integer';
      Description = 'Time zone code that was in use when the record was created.';
      Caption = 'UTC Conversion Time Zone Code';
    }
    field(33;cr703_No;Text[100])
    {
      ExternalName = 'cr703_no';
      ExternalType = 'String';
      Description = 'Required name field';
      Caption = 'No.';
    }
    field(34;cr703_Title;Text[30])
    {
      ExternalName = 'cr703_title';
      ExternalType = 'String';
      Description = '';
      Caption = 'Title';
    }
    field(35;cr703_PageCount;Integer)
    {
      ExternalName = 'cr703_pagecount';
      ExternalType = 'Integer';
      Description = '';
      Caption = 'Page Count';
    }
    field(36;cr703_Hardcover;Boolean)
    {
      ExternalName = 'cr703_hardcover';
      ExternalType = 'Boolean';
      Description = '';
      Caption = 'Hardcover';
    }
    field(38;cr703_Author;Text[30])
    {
      ExternalName = 'cr703_author';
      ExternalType = 'String';
      Description = '';
      Caption = 'Author';
    }
  }
  keys
  {
    key(PK;cr703_LabBookId)
    {
      Clustered = true;
    }
    key(Name;cr703_No)
    {
    }
  }
  fieldgroups
  {
    fieldgroup(DropDown;cr703_No)
    {
    }
  }
}

2. Create a new page, specify the CDS cr703_LabBook integration table as the source table as shown below:

page 50102 "CDS Lab Book List"
{
    Caption = 'CDS Lab Book List';
    PageType = List;
    SourceTable = "CDS cr703_LabBook";
    Editable = false;
    ApplicationArea = All;
    UsageCategory = Lists;
    RefreshOnActivate = true;

    layout
    {
        area(Content)
        {
            repeater(Group)
            {
                field(cr703_No; Rec.cr703_No)
                {
                    Caption = 'No.';
                    ApplicationArea = All;
                }
                field(cr703_Title; Rec.cr703_Title)
                {
                    Caption = 'Title';
                    ApplicationArea = All;
                }
                field(cr703_Author; Rec.cr703_Author)
                {
                    Caption = 'Author';
                    ApplicationArea = All;
                }
                field(cr703_Hardcover; Rec.cr703_Hardcover)
                {
                    Caption = 'Hardcover';
                    ApplicationArea = All;
                }
                field(cr703_PageCount; Rec.cr703_PageCount)
                {
                    Caption = 'Page Count';
                    ApplicationArea = All;
                }
            }
        }
    }

    actions
    {
        area(processing)
        {
            action(CreateFromCDS)
            {
                ApplicationArea = All;
                Caption = 'Create in Business Central';
                Promoted = true;
                PromotedCategory = Process;
                ToolTip = 'Generate the table from the coupled Microsoft Dataverse lab book.';

                trigger OnAction()
                var
                    CDSLabBook: Record "CDS cr703_LabBook";
                    CRMIntegrationManagement: Codeunit "CRM Integration Management";
                begin
                    CurrPage.SetSelectionFilter(CDSLabBook);
                    CRMIntegrationManagement.CreateNewRecordsFromCRM(CDSLabBook);
                end;
            }
        }
    }

    var
        CurrentlyCoupledCDSLabBook: Record "CDS cr703_LabBook";

    trigger OnInit()
    begin
        Codeunit.Run(Codeunit::"CRM Integration Management");
    end;

    procedure SetCurrentlyCoupledCDSLabBook(CDSLabBook: Record "CDS cr703_LabBook")
    begin
        CurrentlyCoupledCDSLabBook := CDSLabBook;
    end;
}
Enable coupling and synchronization between Lab Book in Dataverse and in Business Central

To connect a Business Central table record with a Microsoft Dataverse table row, you create a coupling. A coupling consists of the primary ID, which is typically a GUID, from a Microsoft Dataverse row and the integration ID, also often a GUID, from Business Central.

1. Create a new codeunit.

2. In codeunit CRM Setup Defaults (ID 5334), subscribe to the OnGetCDSTableNo event, as follows:

You can now use the table to create a page for coupling Business Central records with Microsoft Dataverse rows.

3. In codeunit Lookup CRM Tables (ID 5332), subscribe to the OnLookupCRMTables event, as follows:

4. In codeunit CRM Setup Defaults (ID 5334), subscribe to the OnAddEntityTableMapping event in order to enable deep linking between coupled Business Central records and Microsoft Dataverse records.

Note: In the latest version of Business Central, ‘Codeunit “CRM Setup Defaults”‘ does not contain a definition for ‘AddEntityTableMapping’AL(AL0132), so you need to manually add this function as above.

Create default integration table mappings and field mappings

For synchronization to work, mappings must exist to associate the table ID and fields of the integration table (in this case, CDS cr703_LabBook) with the table in Business Central (in this case table Lab Book). There are two types of mapping:

  • Integration table mapping – Integration table mapping links the Business Central table to the integration table for the Microsoft Dataverse table.
  • Integration field mapping – Field mapping links a field in a table row in Microsoft Dataverse with a field in a record in Business Central. This determines which field in Business Central corresponds to which field in Microsoft Dataverse. Typically, there are multiple field mappings for a table.

To create an integration table mapping, we can create the integration table mapping by subscribing to the OnAfterResetConfiguration event in codeunit CDS Setup Defaults (ID 7204).

To create integration fields mappings, similar to the table above.

Codeunit Source Code: Please modify the red part according to your table.

codeunit 50100 CDSDataverseEvent
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnGetCDSTableNo', '', false, false)]
    local procedure HandleOnGetCDSTableNo(BCTableNo: Integer; var CDSTableNo: Integer; var handled: Boolean)
    begin
        if BCTableNo = DATABASE::"Lab Book" then begin
            CDSTableNo := DATABASE::"CDS cr703_LabBook";
            handled := true;
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Lookup CRM Tables", 'OnLookupCRMTables', '', true, true)]
    local procedure HandleOnLookupCRMTables(CRMTableID: Integer; NAVTableId: Integer; SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text; var Handled: Boolean)
    begin
        if CRMTableID = Database::"CDS cr703_LabBook" then
            Handled := LookupCDSLabBook(SavedCRMId, CRMId, IntTableFilter);
    end;

    local procedure LookupCDSLabBook(SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text): Boolean
    var
        CDSLabBook: Record "CDS cr703_LabBook";
        OriginalCDSLabBook: Record "CDS cr703_LabBook";
        OriginalCDSLabBookList: Page "CDS Lab Book List";
    begin
        if not IsNullGuid(CRMId) then begin
            if CDSLabBook.Get(CRMId) then
                OriginalCDSLabBookList.SetRecord(CDSLabBook);
            if not IsNullGuid(SavedCRMId) then
                if OriginalCDSLabBook.Get(SavedCRMId) then
                    OriginalCDSLabBookList.SetCurrentlyCoupledCDSLabBook(OriginalCDSLabBook);
        end;

        CDSLabBook.SetView(IntTableFilter);
        OriginalCDSLabBookList.SetTableView(CDSLabBook);
        OriginalCDSLabBookList.LookupMode(true);
        if OriginalCDSLabBookList.RunModal = ACTION::LookupOK then begin
            OriginalCDSLabBookList.GetRecord(CDSLabBook);
            CRMId := CDSLabBook.cr703_LabBookId;
            exit(true);
        end;
        exit(false);
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnAddEntityTableMapping', '', true, true)]
    local procedure HandleOnAddEntityTableMapping(var TempNameValueBuffer: Record "Name/Value Buffer" temporary);
    begin
        AddEntityTableMapping('labbook', DATABASE::"CDS cr703_LabBook", TempNameValueBuffer);
    end;

    local procedure AddEntityTableMapping(CRMEntityTypeName: Text; TableID: Integer; var TempNameValueBuffer: Record "Name/Value Buffer" temporary)
    begin
        TempNameValueBuffer.Init();
        TempNameValueBuffer.ID := TempNameValueBuffer.Count + 1;
        TempNameValueBuffer.Name := CopyStr(CRMEntityTypeName, 1, MaxStrLen(TempNameValueBuffer.Name));
        TempNameValueBuffer.Value := Format(TableID);
        TempNameValueBuffer.Insert();
    end;

    local procedure InsertIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping"; MappingName: Code[20]; TableNo: Integer; IntegrationTableNo: Integer; IntegrationTableUIDFieldNo: Integer; IntegrationTableModifiedFieldNo: Integer; TableConfigTemplateCode: Code[10]; IntegrationTableConfigTemplateCode: Code[10]; SynchOnlyCoupledRecords: Boolean)
    begin
        IntegrationTableMapping.CreateRecord(MappingName, TableNo, IntegrationTableNo, IntegrationTableUIDFieldNo, IntegrationTableModifiedFieldNo, TableConfigTemplateCode, IntegrationTableConfigTemplateCode, SynchOnlyCoupledRecords, IntegrationTableMapping.Direction::Bidirectional, 'CDS');
    end;

    procedure InsertIntegrationFieldMapping(IntegrationTableMappingName: Code[20]; TableFieldNo: Integer; IntegrationTableFieldNo: Integer; SynchDirection: Option; ConstValue: Text; ValidateField: Boolean; ValidateIntegrationTableField: Boolean)
    var
        IntegrationFieldMapping: Record "Integration Field Mapping";
    begin
        IntegrationFieldMapping.CreateRecord(IntegrationTableMappingName, TableFieldNo, IntegrationTableFieldNo, SynchDirection,
            ConstValue, ValidateField, ValidateIntegrationTableField);
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"CDS Setup Defaults", 'OnAfterResetConfiguration', '', true, true)]
    local procedure HandleOnAfterResetConfiguration(CDSConnectionSetup: Record "CDS Connection Setup")
    var
        IntegrationTableMapping: Record "Integration Table Mapping";
        IntegrationFieldMapping: Record "Integration Field Mapping";
        CDSLabBook: Record "CDS cr703_LabBook";
        LabBook: Record "Lab Book";
    begin
        InsertIntegrationTableMapping(
            IntegrationTableMapping, 'LabBook',
            DATABASE::"Lab Book", DATABASE::"CDS cr703_LabBook", CDSLabBook.FieldNo(cr703_LabBookId), CDSLabBook.FieldNo(ModifiedOn), '', '', true);

        InsertIntegrationFieldMapping('LabBook', LabBook.FieldNo("No."), CDSLabBook.FieldNo(cr703_No), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);
        InsertIntegrationFieldMapping('LabBook', LabBook.FieldNo(Title), CDSLabBook.FieldNo(cr703_Title), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);
        InsertIntegrationFieldMapping('LabBook', LabBook.FieldNo(Author), CDSLabBook.FieldNo(cr703_Author), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);
        InsertIntegrationFieldMapping('LabBook', LabBook.FieldNo(Hardcover), CDSLabBook.FieldNo(cr703_Hardcover), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);
        InsertIntegrationFieldMapping('LabBook', LabBook.FieldNo("Page Count"), CDSLabBook.FieldNo(cr703_PageCount), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);
    end;
}
To create actions on the page for managing coupling and synchronization

To enable users to create couplings between records in the two systems, we can extend the page with actions for creating and deleting couplings, for synchronizing and for deep linking.
For example: The following code example adds those actions to the Lab Book List page. (Recommend to add to Card Page)

page 50101 "Lab Book List"
{
    Caption = 'Lab Book List';
    PageType = List;
    ApplicationArea = All;
    UsageCategory = Administration;
    SourceTable = "Lab Book";
    CardPageId = "Lab Book Card";
    Editable = false;

    layout
    {
        area(Content)
        {
            repeater(Group)
            {
                field("No."; Rec."No.")
                {
                    ApplicationArea = All;

                }
                field(Title; Rec.Title)
                {
                    ApplicationArea = All;
                }
                field(Author; Rec.Author)
                {
                    ApplicationArea = All;
                }
                field(Hardcover; Rec.Hardcover)
                {
                    ApplicationArea = All;
                }
                field("Page Count"; Rec."Page Count")
                {
                    ApplicationArea = All;
                }
            }
        }
    }

    actions
    {
        area(Processing)
        {
            group(ActionGroupCDS)
            {
                Caption = 'Dataverse';
                Visible = CDSIntegrationEnabled;

                action(CDSGotoLabBook)
                {
                    Caption = 'Lab Book';
                    Image = CoupledCustomer;
                    ToolTip = 'Open the coupled Dataverse lab book.';

                    trigger OnAction()
                    var
                        CRMIntegrationManagement: Codeunit "CRM Integration Management";
                    begin
                        CRMIntegrationManagement.ShowCRMEntityFromRecordID(Rec.RecordId);
                    end;
                }
                action(CDSSynchronizeNow)
                {
                    Caption = 'Synchronize';
                    ApplicationArea = All;
                    Visible = true;
                    Image = Refresh;
                    Enabled = CDSIsCoupledToRecord;
                    ToolTip = 'Send or get updated data to or from Microsoft Dataverse.';

                    trigger OnAction()
                    var
                        CRMIntegrationManagement: Codeunit "CRM Integration Management";
                    begin
                        CRMIntegrationManagement.UpdateOneNow(Rec.RecordId);
                    end;
                }
                action(ShowLog)
                {
                    Caption = 'Synchronization Log';
                    ApplicationArea = All;
                    Visible = true;
                    Image = Log;
                    ToolTip = 'View integration synchronization jobs for the customer table.';

                    trigger OnAction()
                    var
                        CRMIntegrationManagement: Codeunit "CRM Integration Management";
                    begin
                        CRMIntegrationManagement.ShowLog(Rec.RecordId);
                    end;
                }
                group(Coupling)
                {
                    Caption = 'Coupling';
                    Image = LinkAccount;
                    ToolTip = 'Create, change, or delete a coupling between the Business Central record and a Microsoft Dataverse row.';

                    action(ManageCDSCoupling)
                    {
                        Caption = 'Set Up Coupling';
                        ApplicationArea = All;
                        Visible = true;
                        Image = LinkAccount;
                        ToolTip = 'Create or modify the coupling to a Microsoft Dataverse lab book.';

                        trigger OnAction()
                        var
                            CRMIntegrationManagement: Codeunit "CRM Integration Management";
                        begin
                            CRMIntegrationManagement.DefineCoupling(Rec.RecordId);
                        end;
                    }
                    action(DeleteCDSCoupling)
                    {
                        Caption = 'Delete Coupling';
                        ApplicationArea = All;
                        Visible = true;
                        Image = UnLinkAccount;
                        Enabled = CDSIsCoupledToRecord;
                        ToolTip = 'Delete the coupling to a Microsoft Dataverse lab book.';

                        trigger OnAction()
                        var
                            CRMCouplingManagement: Codeunit "CRM Coupling Management";
                        begin
                            CRMCouplingManagement.RemoveCoupling(Rec.RecordId);
                        end;
                    }
                }
            }
        }
    }
    trigger OnOpenPage()
    begin
        CDSIntegrationEnabled := CRMIntegrationManagement.IsCDSIntegrationEnabled();
    end;

    trigger OnAfterGetCurrRecord()
    begin
        if CDSIntegrationEnabled then
            CDSIsCoupledToRecord := CRMCouplingManagement.IsRecordCoupledToCRM(Rec.RecordId);
    end;

    var
        CRMIntegrationManagement: Codeunit "CRM Integration Management";
        CRMCouplingManagement: Codeunit "CRM Coupling Management";
        CDSIntegrationEnabled: Boolean;
        CDSIsCoupledToRecord: Boolean;
}
Update the default mappings to include our new integration table mapping

After publishing the extension, we can update the default mappings to include our new integration table mapping by opening the Dataverse Connection Setup page in Business Central and choosing Use Default Synchronization Setup.

Choose Yes.

The default setup for Dataverse synchronization has completed successfully.

Then open Microsoft Dynamics 365 Connection Setup page.

Choose Integration Table Mappings.

You can find the table you customized to add.

Choose Fields on this page.

You can see the fields you have added in codeunit.

Okay, users can now manually synchronize employee records in Business Central with Worker table rows in Microsoft Dataverse from the Business Central client.

But if you synchronize now, it will be failed.

The error message.

Principal user (Id=946233c6-9df9-eb11-94ef-00224803b0a4, type=8, roleCount=3, privilegeCount=112, accessMode=4), is missing prvReadcr703_LabBook privilege (Id=e9b04b6b-d87c-4e01-be18-55ad6bb97407) on OTC=10749 for entity ‘cr703_labbook’ (LocalizedName=’Lab Book’). context.Caller=946233c6-9df9-eb11-94ef-00224803b0a4

The same error message can be confirmed when opening the Dataverse table.

This issue took me a long time. It looks like a permission error for custom entities. But the user I use is a system administrator and has a license for Dynamics 365 Customer Engagement Applications (read-write access mode). And I can open this new table directly on the Dataverse. But when I go to Business Central, it shows this error that I don’t have permission.

Later, I discovered that when synchronizing, it is not based on the current user, but on the permissions of the Application User.

In the Power Platform admin center, select the environment and choose Settings.

Choose User + permissions -> Application users.

There is an application user called Business Central Integration Business Central to Common Data Service. Choose Edit security roles.

You can see the security roles of the application user assigned.

Then go to User + permissions -> Security roles.

Find the role and choose Edit.

In Custom Entities tab, find your custom entities and add permissions

Finally, choose Save and Close.

Now you can open the CDS Lab Book List page, and synchronize data without any error.

Test Video:

In conclusion, this walk through may seem very complicated, but in fact, as long as you understand it, it is not difficult. The following is a simple summary of the process, I hope it can help you.

The complete source code in Github: IntegrationWithDataverse.

For more details from Microsoft Documents:
Customizing an Integration with Microsoft Dataverse

AL Table Proxy Generator

Scheduling a Synchronization between Business Central and Dataverse

Update 2024.03.12: Business Central 2024 wave 1 (BC24): Add table and field mappings to existing integration tables (Dataverse)

END

Hope this will help.

Thanks for reading.

ZHU

コメント

Copied title and URL