Dynamics 365 Business Central: TRANSFERFIELDS and Obsolete fields

Do you know the wonderful C/AL (ops, now AL) command called TRANSFERFIELDS? This command permits you to copy all matching fields in one record to another record:

Record.TRANSFERFIELDS(FromRecord [, InitPrimaryKeyFields])

TRANSFERFIELDS copies fields based on the Field No. Property of the fields. For each field in Record (the destination), the contents of the field that has the same Field No. in FromRecord (the source) will be copied, if such a field exists.

The fields must have the same data type for the copying to succeed (text and code are convertible, other types are not.) There must be room for the actual length of the contents of the field to be copied in the field to which it is to be copied. If any one of these conditions are not fulfilled, a run-time error will occur.

TRANSFERFIELDS is widely used in Microsoft’s Base App code (posting routines and so on) but unfortunately at the moment there’s a problem with this command on Dynamics 365 Business Central: it ignores the ObsoleteState property.

As an example, imagine to have a SOURCE table with the following fields:

Field ID Field Name Field Type ObsoleteState
1 Field1 Code[20]  
2 Field2 Text[100]  
3 Field3 Integer Removed
4 Field4 Decimal  

Here, Field3 was declared with ObsoleteState = Removed (this field will never be used).

Now, consider a DESTINATION table with the following fields:

Field ID Field Name Field Type ObsoleteState
1 Field1 Code[20]  
2 Field2 Text[100]  
3 Field3 Code[20]  
4 Field4 Decimal  

If now in your AL code you execute DESTINATION.TransferFields(SOURCE) you receive an error at runtime (like “the following fields must have the same type“), because the TRANSFERFIELDS command tries also to transfer Field3 from SOURCE to DESTINATION tables (despite the ObsoleteState property) and data type doesn’t match.

There’s also an issue opened on GitHub lots of time ago about this, but no news from Microsoft at the moment.

How to avoid this? Quite difficult (alias impossible) on Microsoft’ Base App code (you cannot modify that code).

For your solutions (extensions), you should implement a “safe TRANSFERFIELDS” command that consider also the ObsoleteState field property. Obviously, also Microsoft should do that on its standard codebase.

Here a possible solution (Microsoft, please check/think on this) of a “safe” TRANSFERFIELDS that:

  • Transfers only fields where ObsoleteState is not set as Removed.
  • Checks the matching data type between source and destination fields (for not throwing errors)
procedure SafeTransferFields(SourceTableID: Integer;TargetTableID: Integer);
var
   SourceRef: RecordRef;
   TargetRef: RecordRef;
   FldRef: FieldRef;
   FieldsSource: Record Field;
   FieldsTarget: Record Field;
   FieldsNoToTransfer: Record Integer temporary;

begin
   FieldsSource.SetRange(TableNo,SourceTableID);
   FieldsSource.SetRange(Class,FieldsSource.Class::Normal);
   FieldsSource.SetRange(Enabled,true);
    FieldsSource.SetFilter(ObsoleteState,'<>%1',FieldsSource.ObsoleteState::Removed);
   IF FieldsSource.FindSet() then
   repeat
     //Check if the field exists in the destination table and if the criteria for the trasfer are satisfied
     if FieldsTarget.GET(TargetTableID,FieldsSource."No.") then
        if (FieldsTarget.Class = FieldsSource.Class) and
            (FieldsTarget.Type = FieldsSource.Type) and  
            (FieldsTarget.ObsoleteState <> FieldsTarget.ObsoleteState::Removed)
        then 
        begin 
          //This field must be transferred
          FieldsNoToTransfer.Number := FieldsSource."No.";
          FieldsNoToTransfer.Insert();
        end;
   until FieldsSource.Next() = 0;

   if FieldsNoToTransfer.IsEmpty then
     exit;  //There are no fields to transfer
  
   //Execute the transferfields of the selected fields
   SourceRef.Open(SourceTableID);
   TargetRef.Open(TargetTableID);
   if SourceRef.FindSet() THEN
   repeat
      FieldsNoToTransfer.FindSet();
      repeat
         FldRef := TargetRef.Field(FieldsNoToTransfer.Number);
         FldRef.Value := SourceRef.Field(FieldsNoToTransfer.Number).Value;
      until FieldsNoToTransfer.Next() = 0;  
      TargetRef.Insert();
   until SourceRef.Next() = 0;
end;

Basically, the command checks all the fields to transfer, saves them in a temporary Integer table and then performs the transfer of these fields by using RecordRef and FieldRef objects.

You can use this “safe TRANSFERFIELDS” in your extensions in order to avoid errors. As said before, Microsoft should do something too…

Comment List
Related
Recommended