Codeunit 7202 Synthetic Relations, source in 29

Source29

src/Layers/W1/BaseApp/Integration/Dataverse/SyntheticRelations.Codeunit.al448 lines, Copyright (c) Microsoft Corporation. MIT

// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Integration.Dataverse;

using Microsoft.Integration.D365Sales;
using Microsoft.Integration.SyncEngine;
using System;
using System.Reflection;
using System.Telemetry;

codeunit 7202 "Synthetic Relations"
{
    var
        VSolutionPrefixTok: Label 'dyn365bc', Locked = true;
        DefaultV2VTLogicalNameTok: Label 'dyn365bc_%1_v2_0', Locked = true;
        FeatureTelemetryNameTok: Label 'Dataverse - Synthetic Relations', Locked = true;

    internal procedure GetFeatureTelemetryName(): Text
    begin
        exit(FeatureTelemetryNameTok);
    end;

    internal procedure CreateSyntheticRelation(SynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary; NativeTableKey: Text[100])
    var
        CDSBCTableRelation: Record "CDS BC Table Relation";
        CRMOrganization: Record "CRM Organization";
        FeatureTelemetry: Codeunit "Feature Telemetry";
        CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
        CustomDimensions: Dictionary of [Text, Text];
        CrmHelper: DotNet CrmHelper;
        NativeFields: array[3] of Text[100];
        ConnectionName: Text;
        KeyName: Text[100];
        LanguageCode: Integer;
    begin
        FeatureTelemetry.LogUptake('0000MQ3', GetFeatureTelemetryName(), Enum::"Feature Uptake Status"::"Set up");
        FeatureTelemetry.LogUptake('0000MQ4', GetFeatureTelemetryName(), Enum::"Feature Uptake Status"::Used);
        ConnectionName := CDSIntegrationImpl.RegisterConfiguredConnection(CrmHelper);
        CRMOrganization.FindFirst();
        LanguageCode := CRMOrganization.LanguageCode;
        NativeFields[1] := SynthRelationMappingBuffer."Syncd. Field 1 External Name";
        NativeFields[2] := SynthRelationMappingBuffer."Syncd. Field 2 External Name";
        NativeFields[3] := SynthRelationMappingBuffer."Syncd. Field 3 External Name";

        if NativeTableKey <> '' then
            KeyName := NativeTableKey
        else
            KeyName := VerifyKeyForTableAndColumnsExist(CRMHelper, SynthRelationMappingBuffer."Syncd. Table External Name", NativeFields, LanguageCode);
        CDSBCTableRelation.dyn365bc_nativeentity := CopyStr(SynthRelationMappingBuffer."Syncd. Table External Name", 1, 100);
        CDSBCTableRelation.dyn365bc_virtualentity := SynthRelationMappingBuffer."Virtual Table Logical Name";
        CDSBCTableRelation.name := GetRelationName(CDSBCTableRelation.dyn365bc_nativeentity, CDSBCTableRelation.dyn365bc_virtualentity);
        CDSBCTableRelation.dyn365bc_referencedattribname1 := SynthRelationMappingBuffer."Syncd. Field 1 External Name";
        CDSBCTableRelation.dyn365bc_referencedattribname2 := SynthRelationMappingBuffer."Syncd. Field 2 External Name";
        CDSBCTableRelation.dyn365bc_referencedattribname3 := SynthRelationMappingBuffer."Syncd. Field 3 External Name";
        CDSBCTableRelation.dyn365bc_referencingattribname1 := SynthRelationMappingBuffer."Virtual Table Column 1 Name";
        CDSBCTableRelation.dyn365bc_referencingattribname2 := SynthRelationMappingBuffer."Virtual Table Column 2 Name";
        CDSBCTableRelation.dyn365bc_referencingattribname3 := SynthRelationMappingBuffer."Virtual Table Column 3 Name";
        CDSBCTableRelation.dyn365bc_nativeentitykey := KeyName;
        InsertSyntheticRelationWithRetry(CDSBCTableRelation);
        UnregisterTableConnection(TableConnectionType::CRM, ConnectionName);
        CustomDimensions.Add('Native Table', CDSBCTableRelation.dyn365bc_nativeentity);
        CustomDimensions.Add('Virtual Table', CDSBCTableRelation.dyn365bc_virtualentity);
        CustomDimensions.Add('Native Fields', CDSBCTableRelation.dyn365bc_referencedattribname1 + ', ' + CDSBCTableRelation.dyn365bc_referencedattribname2 + ', ' + CDSBCTableRelation.dyn365bc_referencedattribname3);
        CustomDimensions.Add('Virtual Fields', CDSBCTableRelation.dyn365bc_referencingattribname1 + ', ' + CDSBCTableRelation.dyn365bc_referencingattribname2 + ', ' + CDSBCTableRelation.dyn365bc_referencingattribname3);
        FeatureTelemetry.LogUsage('0000MQ5', GetFeatureTelemetryName(), 'Created Synthetic Relation through BC wizard.', CustomDimensions)
    end;

    internal procedure LoadExistingBCTableRelations(var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary)
    var
        CDSBCTableRelation: Record "CDS BC Table Relation";
        CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
        CRMHelper: DotNet CrmHelper;
        ConnectionName: Text;
    begin
        ConnectionName := CDSIntegrationImpl.RegisterConfiguredConnection(CrmHelper);
        TempSynthRelationMappingBuffer.Reset();
        TempSynthRelationMappingBuffer.DeleteAll();
        if not CDSBCTableRelation.FindSet() then
            exit;
        repeat
            TempSynthRelationMappingBuffer."Synth. Relation Name" := CDSBCTableRelation.name;
            TempSynthRelationMappingBuffer."Rel. Native Entity Name" := CDSBCTableRelation.dyn365bc_nativeentity;
            TempSynthRelationMappingBuffer."Rel. Virtual Entity Name" := CDSBCTableRelation.dyn365bc_virtualentity;
            TempSynthRelationMappingBuffer."Syncd. Field 1 External Name" := CDSBCTableRelation.dyn365bc_referencedattribname1;
            TempSynthRelationMappingBuffer."Virtual Table Column 1 Name" := CDSBCTableRelation.dyn365bc_referencingattribname1;
            TempSynthRelationMappingBuffer."Syncd. Field 2 External Name" := CDSBCTableRelation.dyn365bc_referencedattribname2;
            TempSynthRelationMappingBuffer."Virtual Table Column 2 Name" := CDSBCTableRelation.dyn365bc_referencingattribname2;
            TempSynthRelationMappingBuffer."Syncd. Field 3 External Name" := CDSBCTableRelation.dyn365bc_referencedattribname3;
            TempSynthRelationMappingBuffer."Virtual Table Column 3 Name" := CDSBCTableRelation.dyn365bc_referencingattribname3;
            TempSynthRelationMappingBuffer."Relation Id" := CDSBCTableRelation.dyn365bc_syntheticrelationId;
            TempSynthRelationMappingBuffer.Insert();
        until CDSBCTableRelation.Next() = 0;
        UnregisterTableConnection(TableConnectionType::CRM, ConnectionName);
    end;

    internal procedure GetLogicalNameFromAPIPhysicalName(PhysicalName: Text): Text[100]
    begin
        exit(CopyStr(VSolutionPrefixTok + '_' + LowerCase(PhysicalName), 1, 100));
    end;

    internal procedure GetVirtualTableFields(var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary; TempSyntheticRelationToCreate: Record "Synth. Relation Mapping Buffer" temporary)
    var
        PageControlField: Record "Page Control Field";
    begin
        PageControlField.SetRange(PageNo, TempSyntheticRelationToCreate."Virtual Table API Page Id");
        if not PageControlField.FindSet() then
            exit;
        repeat
            TempSynthRelationMappingBuffer."Virtual Table Column 1 Name" := CopyStr(PageControlField.ControlName, 1, MaxStrLen(TempSynthRelationMappingBuffer."Virtual Table Column 1 Name"));
            TempSynthRelationMappingBuffer."Virtual Table Column 1 Caption" := PageControlField.ControlName;
            TempSynthRelationMappingBuffer.Insert();
        until PageControlField.Next() = 0;
    end;

    internal procedure LoadVisibleVirtualTables(var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary)
    var
        TempCDSAvVirtualTableBuffer: Record "CDS Av. Virtual Table Buffer" temporary;
        CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
    begin
        CDSIntegrationImpl.LoadAvailableVirtualTables(TempCDSAvVirtualTableBuffer, true);
        if not TempCDSAvVirtualTableBuffer.FindSet() then
            exit;
        repeat
            TempSynthRelationMappingBuffer."Virtual Table Caption" := TempCDSAvVirtualTableBuffer."Display Name";
            TempSynthRelationMappingBuffer."Virtual Table Logical Name" := CopyStr(VSolutionPrefixTok + '_' + TempCDSAvVirtualTableBuffer."CDS Entity Logical Name", 1, 100);
            TempSynthRelationMappingBuffer."Virtual Table Phys. Name" := TempCDSAvVirtualTableBuffer."Phsyical Name";
            TempSynthRelationMappingBuffer.Insert();
        until TempCDSAvVirtualTableBuffer.Next() = 0;
    end;

    internal procedure GetAllAPIPages(var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary)
    var
        PageMetadata: Record "Page Metadata";
    begin
        PageMetadata.SetRange(PageType, PageMetadata.PageType::API);
        if not PageMetadata.FindSet() then
            exit;
        repeat
            TempSynthRelationMappingBuffer."Virtual Table Phys. Name" := CopyStr(PageMetadata.EntityName, 1, MaxStrLen(TempSynthRelationMappingBuffer."Virtual Table Phys. Name"));
            TempSynthRelationMappingBuffer."Virtual Table API Page Id" := PageMetadata.ID;
            TempSynthRelationMappingBuffer."Syncd. Table Id" := PageMetadata.ID;
            TempSynthRelationMappingBuffer.Insert();
        until PageMetadata.Next() = 0;
    end;

    internal procedure TryToGetVirtualTableAPIPageId(VirtualTableSelected: Record "Synth. Relation Mapping Buffer"): Integer
    var
        PageMetadata: Record "Page Metadata";
    begin
        PageMetadata.SetRange(PageType, PageMetadata.PageType::API);
        PageMetadata.SetRange(EntityName, VirtualTableSelected."Virtual Table Phys. Name");
        if not PageMetadata.FindFirst() then
            exit(0);
        exit(PageMetadata.ID);
    end;

    internal procedure GetSynchedIntegrationTables(var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary)
    var
        IntegrationTableMapping: Record "Integration Table Mapping";
        TableMetadata: Record "Table Metadata";
    begin
        IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
        if not IntegrationTableMapping.FindSet() then
            exit;
        repeat
            TempSynthRelationMappingBuffer.SetRange("Syncd. Table Id", IntegrationTableMapping."Integration Table ID");
            if not TempSynthRelationMappingBuffer.FindSet() then begin
                TableMetadata.Get(IntegrationTableMapping."Integration Table ID");
                TableMetadata.TestField(TableType, TableMetadata.TableType::CRM);
                TempSynthRelationMappingBuffer."Syncd. Table Name" := TableMetadata.Name;
                TempSynthRelationMappingBuffer."Syncd. Table External Name" := TableMetadata.ExternalName;
                TempSynthRelationMappingBuffer."Syncd. Table Id" := IntegrationTableMapping."Integration Table ID";
                TempSynthRelationMappingBuffer.Insert();
            end;
        until IntegrationTableMapping.Next() = 0;
        TempSynthRelationMappingBuffer.Reset();
    end;

    internal procedure GetSynchedIntegrationFields(var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary; NativeDataverseTableSelected: Record "Synth. Relation Mapping Buffer")
    var
        IntegrationTableMapping: Record "Integration Table Mapping";
        IntegrationFieldMapping: Record "Integration Field Mapping";
        Field: Record Field;
    begin
        IntegrationTableMapping.SetRange("Integration Table ID", NativeDataverseTableSelected."Syncd. Table Id");
        if not IntegrationTableMapping.FindSet() then
            exit;
        repeat
            IntegrationFieldMapping.SetRange("Integration Table Mapping Name", IntegrationTableMapping.Name);
            IntegrationFieldMapping.SetRange(Status, IntegrationFieldMapping.Status::Enabled);
            IntegrationFieldMapping.SetFilter("Integration Table Field No.", '<>%1', 0);
            if IntegrationFieldMapping.FindSet() then
                repeat
                    TempSynthRelationMappingBuffer.SetRange("Syncd. Field 1 Id", IntegrationFieldMapping."Integration Table Field No.");
                    if not TempSynthRelationMappingBuffer.FindSet() then begin
                        Field.Get(NativeDataverseTableSelected."Syncd. Table Id", IntegrationFieldMapping."Integration Table Field No.");
                        TempSynthRelationMappingBuffer."Syncd. Field 1 Name" := Field."Field Caption";
                        TempSynthRelationMappingBuffer."Syncd. Field 1 External Name" := Field.ExternalName;
                        TempSynthRelationMappingBuffer."Syncd. Field 1 Id" := IntegrationFieldMapping."Integration Table Field No.";
                        TempSynthRelationMappingBuffer.Insert();
                    end;
                until IntegrationFieldMapping.Next() = 0;
        until IntegrationTableMapping.Next() = 0;
        TempSynthRelationMappingBuffer.Reset();
    end;

    internal procedure DeleteSyntheticRelation(SynthRelationMapping: Record "Synth. Relation Mapping Buffer")
    var
        CDSBCTableRelation: Record "CDS BC Table Relation";
        CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
        CrmHelper: DotNet CrmHelper;
        ConnectionName: Text;
    begin
        ConnectionName := CDSIntegrationImpl.RegisterConfiguredConnection(CrmHelper);
        CDSBCTableRelation.Get(SynthRelationMapping."Relation Id");
        CDSBCTableRelation.Delete();
        UnregisterTableConnection(TableConnectionType::CRM, ConnectionName);
    end;

    internal procedure GetDefaultSyntheticRelations(var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary)
    var
        TempCRMAccount: Record "CRM Account" temporary;
    begin
        GetNativeTableDetails(Database::"CRM Account", TempCRMAccount.FieldNo(AccountNumber), TempSynthRelationMappingBuffer);
        GetVirtualTableDetails('salesOrder', 'customerNumber', TempSynthRelationMappingBuffer);
        TempSynthRelationMappingBuffer.Insert();
        GetVirtualTableDetails('salesShipment', 'customerNumber', TempSynthRelationMappingBuffer);
        TempSynthRelationMappingBuffer.Insert();
        GetVirtualTableDetails('salesInvoice', 'customerNumber', TempSynthRelationMappingBuffer);
        TempSynthRelationMappingBuffer.Insert();
        GetVirtualTableDetails('salesCreditMemo', 'customerNumber', TempSynthRelationMappingBuffer);
        TempSynthRelationMappingBuffer.Insert();
        GetVirtualTableDetails('customerPayment', 'customerNumber', TempSynthRelationMappingBuffer);
        TempSynthRelationMappingBuffer.Insert();
        GetVirtualTableDetails('customerFinancialDetail', 'number', TempSynthRelationMappingBuffer);
        TempSynthRelationMappingBuffer.Insert();
        GetVirtualTableDetails('purchaseReceipt', 'vendorNumber', TempSynthRelationMappingBuffer);
        TempSynthRelationMappingBuffer.Insert();
        GetVirtualTableDetails('purchaseInvoice', 'vendorNumber', TempSynthRelationMappingBuffer);
        TempSynthRelationMappingBuffer.Insert();
        GetVirtualTableDetails('vendorPayment', 'vendorNumber', TempSynthRelationMappingBuffer);
        TempSynthRelationMappingBuffer.Insert();
    end;

    internal procedure GetExistingAlternateKeyName(NativeEntity: Text[248]; var NativeFields: array[3] of Text[100]; var KeyName: Text[100]; var KeyNames: List of [Text]) Result: Boolean
    var
        CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
        ConnectionName: Text;
        CrmHelper: DotNet CrmHelper;
    begin
        ConnectionName := CDSIntegrationImpl.RegisterConfiguredConnection(CrmHelper);
        Result := GetExistingAlternateKeyName(CrmHelper, NativeEntity, NativeFields, KeyName, KeyNames);
        UnregisterTableConnection(TableConnectionType::CRM, ConnectionName);
    end;

    internal procedure CreateAlternateKeyForColumns(NativeEntity: Text[248]; NativeFields: array[3] of Text[100]; var ExistingKeyNames: List of [Text])
    var
        CRMOrganization: Record "CRM Organization";
        CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
        ConnectionName: Text;
        LanguageCode: Integer;
        CrmHelper: DotNet CrmHelper;
    begin
        ConnectionName := CDSIntegrationImpl.RegisterConfiguredConnection(CrmHelper);
        CRMOrganization.FindFirst();
        LanguageCode := CRMOrganization.LanguageCode;
        CreateAlternateKeyForColumns(CrmHelper, NativeEntity, NativeFields, ExistingKeyNames, LanguageCode);
        UnregisterTableConnection(TableConnectionType::CRM, ConnectionName);
    end;

    internal procedure DoesSynthethicRelationExist(var TempExistingBCTablesRelations: Record "Synth. Relation Mapping Buffer" temporary; TempRelationToFind: Record "Synth. Relation Mapping Buffer" temporary) Exists: Boolean
    begin
        TempExistingBCTablesRelations.Reset();
        TempExistingBCTablesRelations.SetRange("Rel. Native Entity Name", TempRelationToFind."Rel. Native Entity Name");
        TempExistingBCTablesRelations.SetRange("Rel. Virtual Entity Name", TempRelationToFind."Rel. Virtual Entity Name");
        TempExistingBCTablesRelations.SetRange("Syncd. Field 1 External Name", TempRelationToFind."Syncd. Field 1 External Name");
        Exists := not TempExistingBCTablesRelations.IsEmpty();
        TempExistingBCTablesRelations.Reset();
    end;

    local procedure GetNativeTableDetails(TableId: Integer; FieldId: Integer; var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary)
    begin
        TempSynthRelationMappingBuffer."Syncd. Table Id" := TableId;
        GetTableDetailsFromTableId(TempSynthRelationMappingBuffer);
        TempSynthRelationMappingBuffer."Syncd. Field 1 Id" := FieldId;
        GetFieldDetailsFromFieldId(TempSynthRelationMappingBuffer);
    end;

    local procedure GetVirtualTableDetails(EntityName: Text; FieldName: Text; var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary)
    begin
        TempSynthRelationMappingBuffer."Virtual Table Logical Name" := CopyStr(StrSubstNo(DefaultV2VTLogicalNameTok, LowerCase(EntityName)), 1, MaxStrLen(TempSynthRelationMappingBuffer."Virtual Table Logical Name"));
        TempSynthRelationMappingBuffer."Virtual Table Caption" := CopyStr(EntityName, 1, MaxStrLen(TempSynthRelationMappingBuffer."Virtual Table Caption"));
        TempSynthRelationMappingBuffer."Virtual Table Column 1 Name" := CopyStr(VSolutionPrefixTok + '_' + LowerCase(FieldName), 1, MaxStrLen(TempSynthRelationMappingBuffer."Virtual Table Column 1 Name"));
        TempSynthRelationMappingBuffer."Virtual Table Column 1 Caption" := CopyStr(FieldName, 1, MaxStrLen(TempSynthRelationMappingBuffer."Virtual Table Column 1 Caption"));
        TempSynthRelationMappingBuffer."Rel. Virtual Entity Name" := TempSynthRelationMappingBuffer."Virtual Table Logical Name";
    end;

    local procedure GetFieldDetailsFromFieldId(var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary)
    var
        Field: Record Field;
    begin
        Field.Get(TempSynthRelationMappingBuffer."Syncd. Table Id", TempSynthRelationMappingBuffer."Syncd. Field 1 Id");
        TempSynthRelationMappingBuffer."Syncd. Field 1 Name" := Field.FieldName;
        TempSynthRelationMappingBuffer."Syncd. Field 1 External Name" := Field.ExternalName;
    end;

    local procedure GetTableDetailsFromTableId(var TempSynthRelationMappingBuffer: Record "Synth. Relation Mapping Buffer" temporary)
    var
        TableMetadata: Record "Table Metadata";
    begin
        TableMetadata.Get(TempSynthRelationMappingBuffer."Syncd. Table Id");
        TempSynthRelationMappingBuffer."Syncd. Table Name" := TableMetadata.Name;
        TempSynthRelationMappingBuffer."Syncd. Table External Name" := TableMetadata.ExternalName;
        TempSynthRelationMappingBuffer."Rel. Native Entity Name" := CopyStr(TableMetadata.ExternalName, 1, MaxStrLen(TempSynthRelationMappingBuffer."Rel. Native Entity Name"));
    end;

    local procedure GetExistingAlternateKeyName(var CRMHelper: DotNet CrmHelper; NativeEntity: Text[248]; var NativeFields: array[3] of Text[100]; var KeyName: Text[100]; var KeyNames: List of [Text]): Boolean
    var
        ExistingAlternateKeys: DotNet GenericDictionary2;
        Columns: DotNet GenericIEnumerable1;
        ExistingKeyName: Text[100];
    begin
        ExistingAlternateKeys := CRMHelper.RetrieveEntityAlternateKeys(NativeEntity);
        foreach ExistingKeyName in ExistingAlternateKeys.Keys() do begin
            KeyNames.Add(ExistingKeyName);
            Columns := ExistingAlternateKeys.Item(ExistingKeyName);
            if ColumnsInKeyMatchNativeFields(Columns, NativeFields) then begin
                KeyName := ExistingKeyName;
                exit(true);
            end;
        end;
        exit(false);
    end;

    local procedure GetRelationName(NativeEntity: Text[100]; VirtualEntity: Text[100]): Text[100]
    var
        Suffix: Text;
    begin
        Suffix := CreateGuid();
        Suffix := Suffix.Substring(2, 4);
        exit(NativeEntity.Substring(1, 4) + VirtualEntity.Substring(StrLen(VSolutionPrefixTok) + 2, 4) + Suffix);
    end;

    local procedure VerifyKeyForTableAndColumnsExist(var CRMHelper: DotNet CrmHelper; NativeEntity: Text[248]; var NativeFields: array[3] of Text[100]; LanguageCode: Integer): Text[100]
    var
        KeyNames: List of [Text];
        KeyName: Text[100];
    begin
        if GetExistingAlternateKeyName(CRMHelper, NativeEntity, NativeFields, KeyName, KeyNames) then
            exit(KeyName);
        exit(CreateAlternateKeyForColumns(CRMHelper, NativeEntity, NativeFields, KeyNames, LanguageCode));
    end;

    local procedure ColumnsInKeyMatchNativeFields(var Columns: DotNet GenericIEnumerable1; var NativeFields: array[3] of Text[100]): Boolean
    var
        Matched: array[3] of Boolean;
        FieldName: Text;
        Index: Integer;
        ColsRemainingToMatch: Boolean;
    begin
        for Index := 1 to 3 do
            Matched[Index] := NativeFields[Index] = '';
        foreach FieldName in Columns do begin
            ColsRemainingToMatch := false;
            for Index := 1 to 3 do
                if not Matched[Index] then begin
                    ColsRemainingToMatch := true;
                    if FieldName = NativeFields[Index] then
                        Matched[Index] := true;
                end;
            if not ColsRemainingToMatch then
                exit(true);
        end;
        for Index := 1 to 3 do
            if not Matched[Index] then
                exit(false);
        exit(true);
    end;

    local procedure CreateAlternateKeyForColumns(var CRMHelper: DotNet CrmHelper; NativeEntity: Text[248]; NativeFields: array[3] of Text[100]; var ExistingKeyNames: List of [Text]; LanguageCode: Integer): Text[100]
    var
        NewKeyName: Text[100];
        Columns: DotNet Array;
        Index, ColIndex, NColumns : Integer;
    begin
        for Index := 1 to 3 do
            if NativeFields[Index] <> '' then
                NColumns := NColumns + 1;
        GetNewDotNetStringArrayInstance(NColumns, Columns);
        ColIndex := 0;
        for Index := 1 to 3 do
            if NativeFields[Index] <> '' then begin
                Columns.SetValue(NativeFields[Index], ColIndex);
                ColIndex := ColIndex + 1;
            end;
        NewKeyName := GetNewKeyName(ExistingKeyNames, NativeEntity);
        if LanguageCode <> 0 then
            CRMHelper.CreateEntityAlternateKey(NewKeyName, Columns, NativeEntity, LanguageCode)
        else
            CRMHelper.CreateEntityAlternateKey(NewKeyName, Columns, NativeEntity);
        Sleep(5000);
        exit(NewKeyName);
    end;

    local procedure GetNewDotNetStringArrayInstance(Length: Integer; var NewArray: DotNet Array)
    var
        DummyString: DotNet String;
    begin
        DummyString := '';
        NewArray := NewArray.CreateInstance(DummyString.GetType(), Length);
    end;

    local procedure GetNewKeyName(ExistingKeyNames: List of [Text]; EntityName: Text): Text[100]
    var
        NewKeyName: Text[100];
    begin
        NewKeyName := GenerateRandomAlternateKeyName(EntityName);
        while ExistingKeyNames.Contains(NewKeyName) do
            NewKeyName := GenerateRandomAlternateKeyName(EntityName);
        exit(NewKeyName);
    end;

    local procedure GenerateRandomAlternateKeyName(EntityName: Text): Text[100]
    var
        Suffix: Text;
    begin
        Suffix := CreateGuid();
        exit(EntityName.SubString(1, 4) + '_' + Suffix.Substring(2, 4));
    end;

    local procedure InsertSyntheticRelationWithRetry(var CDSBCTableRelation: Record "CDS BC Table Relation")
    begin
        if TryInsertingSyntheticRelation(CDSBCTableRelation) then
            exit;
        Sleep(5000);
        CDSBCTableRelation.Insert();
    end;

    [TryFunction()]
    local procedure TryInsertingSyntheticRelation(var CDSBCTableRelation: Record "CDS BC Table Relation")
    begin
        CDSBCTableRelation.Insert()
    end;

}