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…
UPDATE: it seems that Microsoft after this post has fixed the bug. Official response: “A new overload has been added to the TransferFields method that takes a 3rd parameter that allows you to SkipFieldsNotMatchingType. It will be available with 2019 Wave 2 CU2 and with the 2020 Wave 1 releases. The default behavior for any of the existing transferfields methods and new overload will be to skip the RemoveObsolete parameter.
The new overload will allow to skip not matching fields by type.“
The code provided might have issues with BLOB and Media type fields ?
LikeLike
Good question, not tested with BLOB and Media. But if I remember correctly from NAV 2009 (at least) you can do something like:
BlobFieldRef.VALUE := BlobFieldRef2.VALUE
to transfer a BLOB field.
LikeLike
This is a nice workaround. I hope Microsoft changes the TRANSFERFIELD method to consider ObsoleteState property. Could be a good plan to add a parameter to the TRANSFERFIELD method to get control about this scenario.
Record.TRANSFERFIELD(FromSource[,InitPrimaryKeyFields,SkipObsoleteRemovedFields]);
LikeLiked by 1 person
Hi Stefano, Symbols in BC V15 Doesn’t exist Integer Table. How was possible definitions in your function? Maybe it’s lower than V15?
LikeLike
Integer record still exists. However, this bug is now solved in the next cumulative update.
LikeLike
Thanks Stefano for your quick reply,
Yes, exists. But not in symbol. Then is like not for extensions.
Finally the function is under lower than BC V15 ok?
Best Regards,
LikeLike
You can use Integer record for extensions too…
LikeLike
Thanks Stefano,
Please tell us how to do. Trying to set Integer under V15 symbols show error: Table ‘Integer’ is missing.
Thanks
LikeLike
This piece of code works on version 15:
local procedure TEST()
var
IntegerTable: Record Integer;
begin
IntegerTable.SetFilter(Number,’1..99999′);
end;
LikeLike
Thanks Stefano for your answer,
Unfortunately, it’s not working for us. As you can see in the image below.
https://ibb.co/MDWfW9G
Please, check the image. Our Version: 15.0.36626. Is impossible to set var Integer.
Would it be possible to share with us a screenshot with an extension compiled?
Including all the tree with the symbols.
I hope it’s not an inconvinient for you.
Kind regards,
Diego.
LikeLike
You have a Microsoft app missing on your symbols. Symbols should be the following:
Microsoft_Base Application_15.0.36560.0.app
Microsoft_System Application_15.0.36560.0.app
Microsoft_System_15.0.36510.0.app
LikeLike
Great!
Is needed to set “platform” parameter on app.json file.
Thanks so much Stefano.!
Kind regards.
LikeLike
if Sales Line has a table extension and Sales Inv Line has a table extension with same fields as sales Line extension. Will transferfields also populate the table extensions.
LikeLike
If the field ID is the same, yes.
LikeLike