followed documentation : https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/administration-custom-cds-integration docs and https://yzhums.com/17065/
When I launch “Run Full Sync.” and look at the job log of my testing table, only records initially created in business central are inserted in the crm. Any rows that I created from my integration table in the crm are not inserted in business central.
I suspect that this note below may be the cause because some columns were skipped during the creation of the proxy table using AL Table Proxy Generator butI have no way to know since no error messages are showing up regarding that.
https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-al-table-proxy-generator
Example
Let's say I have 2 records in my testing table on the crm side.
I can see them in my proxy Table List on the business central side, but they are not inserted yet in my target table.
Note for the first screen shot: If I select one of those two rows in my proxy testing table and click on create in business central, it creates it successfully in bc... so I'm wondering why that is working and not the "run full sync"
Then I create a record from bc before running the full synchronization and look at the results.
We can see here that one record was inserted from BC to the crm (all fields were mapped correctly) but nothing from the crm to BC.
Here's my project folder tree. If you need to see a specific page let me know.
CODE Tab50100.Testingtableforintegration.al
table 50100 "Testing table for integration"
{
Caption = 'Target integration table';
DataClassification = ToBeClassified;
fields
{
field(1; "name"; Code[25])
{
Caption = 'name';
DataClassification = ToBeClassified;
}
field(2; "my bc field one"; Text[50])
{
Caption = 'my bc field one';
DataClassification = ToBeClassified;
}
field(3; "my bc field two"; Integer)
{
Caption = 'my bc field two';
DataClassification = ToBeClassified;
}
}
keys
{
key(PK; "name")
{
Clustered = true;
}
}
}
Pag50101.TestingIntrecordslist.al
page 50101 "target table records list"
{
ApplicationArea = All;
Caption = 'target table records list';
PageType = List;
SourceTable = "Testing table for integration";
UsageCategory = Lists;
CardPageId = "target table Card";
Editable = false;
layout
{
area(content)
{
repeater(General)
{
field("code"; Rec."name")
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the code field.';
}
field("my bc field two"; Rec."my bc field two")
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the my bc field two field.';
}
field("my bc field one"; Rec."my bc field one")
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the my bc field one field.';
}
field(SystemCreatedAt; Rec.SystemCreatedAt)
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the SystemCreatedAt field.';
}
field(SystemCreatedBy; Rec.SystemCreatedBy)
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the SystemCreatedBy field.';
}
}
}
}
actions
{
area(Processing)
{
group(ActionGroupCDS)
{
Caption = 'Dataverse';
Visible = CDSIntegrationEnabled;
action(CDSGotoTargetRecord)
{
Caption = 'Go to Dataverse record';
ApplicationArea = All;
Visible = true;
Image = CoupledCustomer;
Enabled = CDSIsCoupledToRecord;
ToolTip = 'Open the coupled Dataverse Record';
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 Record';
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 Record';
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;
}
Pag50100.TestingIntegrationCard.al
page 50100 "target table Card"
{
Caption = 'target table Card';
PageType = Card;
SourceTable = "Testing table for integration";
layout
{
area(content)
{
group(General)
{
field("code"; Rec."name")
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the code field.';
}
field("my bc field one"; Rec."my bc field one")
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the my bc field one field.';
}
field("my bc field two"; Rec."my bc field two")
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the my bc field two field.';
}
field(SystemCreatedAt; Rec.SystemCreatedAt)
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the SystemCreatedAt field.';
}
field(SystemCreatedBy; Rec.SystemCreatedBy)
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the SystemCreatedBy field.';
}
field(SystemId; Rec.SystemId)
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the SystemId field.';
}
}
}
}
}
Generated Proxy Table.al
table 50101 "CDS cr46d_d365salestestingtableforbcintegration"
{
ExternalName = 'cr46d_d365salestestingtableforbcintegration';
TableType = CDS;
Description = '';
fields
{
field(1; cr46d_D365SalestestingtableforbcintegrationId; GUID)
{
ExternalName = 'cr46d_d365salestestingtableforbcintegrationid';
ExternalType = 'Uniqueidentifier';
ExternalAccess = Insert;
Description = 'Unique identifier for entity instances';
Caption = 'D365 Sales testing table for bc integration';
}
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(25; statecode; Option)
{
ExternalName = 'statecode';
ExternalType = 'State';
ExternalAccess = Modify;
Description = 'Status of the D365 Sales testing table for bc integration';
Caption = 'Status';
InitValue = " ";
OptionMembers = " ",Active,Inactive;
OptionOrdinalValues = -1, 0, 1;
}
field(27; statuscode; Option)
{
ExternalName = 'statuscode';
ExternalType = 'Status';
Description = 'Reason for the status of the D365 Sales testing table for bc integration';
Caption = 'Status Reason';
InitValue = " ";
OptionMembers = " ",Active,Inactive;
OptionOrdinalValues = -1, 1, 2;
}
field(29; VersionNumber; BigInteger)
{
ExternalName = 'versionnumber';
ExternalType = 'BigInt';
ExternalAccess = Read;
Description = 'Version Number';
Caption = 'Version Number';
}
field(30; ImportSequenceNumber; Integer)
{
ExternalName = 'importsequencenumber';
ExternalType = 'Integer';
ExternalAccess = Insert;
Description = 'Sequence number of the import that created this record.';
Caption = 'Import Sequence Number';
}
field(31; OverriddenCreatedOn; Date)
{
ExternalName = 'overriddencreatedon';
ExternalType = 'DateTime';
ExternalAccess = Insert;
Description = 'Date and time that the record was migrated.';
Caption = 'Record Created On';
}
field(32; TimeZoneRuleVersionNumber; Integer)
{
ExternalName = 'timezoneruleversionnumber';
ExternalType = 'Integer';
Description = 'For internal use only.';
Caption = 'Time Zone Rule Version Number';
}
field(33; 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(34; cr46d_Name; Text[100])
{
ExternalName = 'cr46d_name';
ExternalType = 'String';
Description = '';
Caption = 'Name';
}
field(35; cr46d_mytestingcolumnone; Text[100])
{
ExternalName = 'cr46d_mytestingcolumnone';
ExternalType = 'String';
Description = '';
Caption = 'my testing column one';
}
field(36; cr46d_mytestingcolumntwo; Integer)
{
ExternalName = 'cr46d_mytestingcolumntwo';
ExternalType = 'Integer';
Description = '';
Caption = 'my testing column two';
}
field(37; cr46d_CoupledtoCRM; Boolean)
{
ExternalName = 'cr46d_coupledtocrm';
ExternalType = 'Boolean';
Description = '';
Caption = 'Coupled to CRM';
}
}
keys
{
key(PK; cr46d_D365SalestestingtableforbcintegrationId)
{
Clustered = true;
}
key(Name; cr46d_Name)
{
}
}
fieldgroups
{
fieldgroup(DropDown; cr46d_Name)
{
}
}
}
Pag50102.ProxyTableList.al
// List of records of the proxy table that shows us the records imported from Dynamics Sales CRM
// each table we want to import need to have its corresponding proxy table. Even the default tables like contact has its proxy tablke that have been automaticaly created by the connection setup.
page 50102 "Proxy Table List"
{
ApplicationArea = All;
Caption = 'Proxy testing Table - Dataverse';
PageType = List;
SourceTable = "CDS cr46d_d365salestestingtableforbcintegration";
UsageCategory = Lists;
layout
{
area(content)
{
repeater(General)
{
field(cr46d_Name; Rec.cr46d_Name)
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the Name field.';
}
field(cr46d_mytestingcolumnone; Rec.cr46d_mytestingcolumnone)
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the my testing column one field.';
}
field(cr46d_mytestingcolumntwo; Rec.cr46d_mytestingcolumntwo)
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the my testing column two field.';
}
field(cr46d_D365SalestestingtableforbcintegrationId; Rec.cr46d_D365SalestestingtableforbcintegrationId)
{
ApplicationArea = All;
ToolTip = 'Specifies the value of the D365 Sales testing table for bc integration field.';
}
}
}
}
actions
{
area(processing)
{
action(CreateFromCDS)
{
ApplicationArea = All;
Caption = 'Create in Business Central';
Promoted = true;
PromotedCategory = Process;
ToolTip = 'Generate the record from the coupled Microsoft Dataverse row.';
Image = AssemblyBOM;
trigger OnAction()
var
// initialise proxy table variable and CRMIntegrationManagement code unit
ProxyTable: Record "CDS cr46d_d365salestestingtableforbcintegration";
CRMIntegrationManagement: Codeunit "CRM Integration Management";
begin
// apply the actual current page filter (selected record) on the proxy table records and pass it to the
// CreateNewRecordsFromCRM function for insertion in the target table
CurrPage.SetSelectionFilter(ProxyTable);
CRMIntegrationManagement.CreateNewRecordsFromCRM(ProxyTable);
end;
}
}
}
var
CurrentlyCoupledProxyTable: Record "CDS cr46d_d365salestestingtableforbcintegration";
// is launched before the startup of the page and calls the OnRun() function of codeunit 5330 "CRM Integration Management", which then
// execute CheckOrEnableCRMConnection to establish the connection with dataverse.
trigger OnInit()
begin
Codeunit.Run(Codeunit::"CRM Integration Management");
end;
// this function is used by the codeunit dataverse event
procedure SetCurrentlyCoupledProxyTable(ProxyTable: Record "CDS cr46d_d365salestestingtableforbcintegration")
begin
CurrentlyCoupledProxyTable := ProxyTable;
end;
}
Cod50100.DataverseEvent.al
// note to read comments: entry here means a line in a table or a single record.
// documentation: https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/administration-custom-cds-integration
codeunit 50100 DataverseEvent
{
// in the codeunit CRM Setup Defaults, when the procedure OnGetCDSTableNo is called, execute this local procedure.
// launched when button setup couplings in BC is clicked
[EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnGetCDSTableNo', '', false, false)]
local procedure HandleOnGetCDSTableNo(BCTableNo: Integer; var CDSTableNo: Integer; var handled: Boolean)
begin
// error('HandleOnGetCDSTableNo was called! %1', BCTableNo);
// specify to the function OnGetCDSTableNo to exit immediatly due to handled := true because BCTableNo and CDSTableNo
// are not default BC tables and will not be found in the codeunit CRM Setup Defaults. (For more details see codeunit 5334)
if BCTableNo = DATABASE::"Testing table for integration" then begin
CDSTableNo := DATABASE::"CDS cr46d_d365salestestingtableforbcintegration";
handled := true; // handled means here taken care of by us.
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 cr46d_d365salestestingtableforbcintegration" then
Handled := LookupProxyTable(SavedCRMId, CRMId, IntTableFilter); // this will return true if CRMID point to a proxy table entry.
end;
// this function returns a boolean used to assign Handled var in HandleOnLookupCRMTables
// the goal of this function is to replicate the procedure LookupCRM<tableName>(we can find the definitions of those functions in the codeunit 5332 - Lookup CRM Tables)
// as if we were to add it directly (but we can't because microsoft don't want their code touched, that is why they specified a event trigger that we can use to extend their codeunit)
local procedure LookupProxyTable(SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text): Boolean
var
ProxyTable: Record "CDS cr46d_d365salestestingtableforbcintegration"; // the proxy table
OriginalProxyTable: Record "CDS cr46d_d365salestestingtableforbcintegration";
OriginalProxyTableList: Page "Proxy Table List"; // THIS IS A LIST NOT A CARD
begin
if not IsNullGuid(CRMId) then begin // if the crm id is not null. The crm id represents the id of an entry in the proxy table
if ProxyTable.Get(CRMId) then // if the proxy table contains the entry linked with the crm id
OriginalProxyTableList.SetRecord(ProxyTable); // Set the proxy table list to the proxy table that has been filtrated by the passed crm id
if not IsNullGuid(SavedCRMId) then// ????
if OriginalProxyTable.Get(SavedCRMId) then begin
Message('we are here in LookupProxyTable');
OriginalProxyTableList.SetCurrentlyCoupledProxyTable(OriginalProxyTable);
end;
;
// gives to the list the currently coupled proxy table
// OriginalProxyTableList.SetCurrentlyCoupledProxyTable(OriginalProxyTable);
end;
ProxyTable.SetView(IntTableFilter);
OriginalProxyTableList.SetTableView(ProxyTable);
OriginalProxyTableList.LookupMode(true);
if OriginalProxyTableList.RunModal = ACTION::LookupOK then begin
OriginalProxyTableList.GetRecord(ProxyTable);
CRMId := ProxyTable.cr46d_D365SalestestingtableforbcintegrationId;
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);
var
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
begin
CRMSetupDefaults.AddEntityTableMapping('cr46d_d365salestestingtableforbcintegration', DATABASE::"CDS cr4', DATABASE::"Testing table for integration", TempNameValueBuffer); //IMPORTANT TO CHANGE - LOGICAL NAME OF THE DV TABLE
CRMSetupDefaults.AddEntityTableMapping('cr46d_d365salestestingtableforbcintegration', DATABASE::"CDS cr46d_d365salestestingtableforbcintegration", TempNameValueBuffer);
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";
ProxyTable: Record "CDS cr46d_d365salestestingtableforbcintegration";
TargetTable: Record "Testing table for integration";
begin
// inserts a line in the integrationTableMapping
InsertIntegrationTableMapping(
IntegrationTableMapping, 'My testing table',
DATABASE::"Testing table for integration", DATABASE::"CDS cr46d_d365salestestingtableforbcintegration", ProxyTable.FieldNo(cr46d_D365SalestestingtableforbcintegrationId), ProxyTable.FieldNo(ModifiedOn), '', '', true);
// in the newly insertion, insert integration fields to couple
InsertIntegrationFieldMapping('My testing table', TargetTable.FieldNo("name"), ProxyTable.FieldNo(cr46d_name), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);
InsertIntegrationFieldMapping('My testing table', TargetTable.FieldNo("my bc field one"), ProxyTable.FieldNo(cr46d_mytestingcolumnone), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);
InsertIntegrationFieldMapping('My testing table', TargetTable.FieldNo("my bc field two"), ProxyTable.FieldNo(cr46d_mytestingcolumntwo), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);
end;
}
So I would like to know first of all what is causing the synchronization to malfunction without raising any messages & how to solve it step by step/in detail if modifications are needed in the project code itself. Hope this was clear enough to illustrate my issue, if any additional clarifications are needed please let me know.
Thank you very much!
I solved my Issue, my proxy table was missing the field modified by from Dataverse. You can see the error message in the job queue entries.