Business central blog
Subscribe to blog and get news about new posts.

What's wrong with RecordRef.Rename() method?

I would like to discuss some strange things with the RecordRef.Rename() method. And I don't even mean the logical limitations that you can observe because of SQL server mechanisms. Let's take a closer look at the design of this method.

Rename(Value1: Any [, Value2: Any,...])

Let's take the Sales Order number change as an example. We will declare a variable SalesHeader and change the number of an existing order to a new one.

procedure RenameSalesOrder10000()
var
  SalesHeader: Record "Sales Header";
begin
  //Get Order '10000'
  SalesHeader.Get(SalesHeader."Document Type"::Order, '10000');
  //Change value of "No." to '42' (part of Primary Key)
  SalesHeader.Rename(SalesHeader."Document Type"::Order, '42');
end;
As we see, to change the value of one of the primary key fields of a table, we need to specify each value. Besides, it is important to pass the values in the same order in which these values are declared in the primary key of the table.
It makes sense. Because if we don't write a complete list of values, there will be an attempt to change the PK to default values. If the values are in the wrong order, we will just mistakenly change the wrong values.

//Error due to type mismatch
//System will try change enum(integer) "Document Type" to '42'(text) and "No." to empty value
SalesHeader.Rename('42');

//Error because you can't change value to initial e.g. int/enum/option 0 or empty text
//System will try change enum(integer) "Document Type" to Quote(0) and "No." to '42'
SalesHeader.Rename(SalesHeader."Document Type"::Quote, '42');

//Error due because you try to pass empty value to "No."
//System will try change enum(integer) "Document Type" to Order and "No." to empty value
SalesHeader.Rename(SalesHeader."Document Type"::Order);
There is no problem here and it is absolutely clear why it works this way. All we need is to know the order and the list of values of the Primary Key even if we want to change only one value from the key.
Stop. But do we always know this information? What do we do when we work with the RecordRef data type?

Problem

Of course! We have a way to determine the number of fields and their values in Primary Key. Even though the table can be anything (due to RecordRef design). There are special functions and data types for this, such as KeyRef, FieldRef, FieldCount(), FieldIndex(), KeyIndex() and so on.
Let's write a method that will determine the order and values of the Primary Key fields from a random RecordRef table.

local procedure GetRecRefPKFieldNosInOrder(RecRef: RecordRef) ListOfPKFieldNo: List of [Integer]
var
  FieldRefVar: FieldRef;
  KeyRefVar: KeyRef;
  KeyCount: Integer;
begin
  //Usually Primary Key is first key of table, so we just get key №1 and assign it to KeyRef variable
  KeyRefVar := RecRef.KeyIndex(1);
  
  //Read each field index from key in loop ​
  for KeyCount := 1 to KeyRefVar.FieldCount() do begin
  
    //Get each field to FieldRef variable by field index
    FieldRefVar := KeyRefVar.FieldIndex(KeyCount);

    //Add each field number to list
    //We can't use values just because values can have different data types
    //List doesn't support variant data type, so we just store numbers for now
    ListOfPKFieldNo.Add(FieldRefVar.Number());

  end;
end;
Cool, now we have the ability to change the Primary Key values of a certain field, even if we don't know which specific table is expected. Just call Rename() method right? But, it's not that simple... Now we get to the problem I encountered when developing the Data Editor tool.

procedure RenamePKValueOfRecRef(var RecRef: RecordRef; FieldNoToChange: Integer; NewValueAsVariant: Variant)
var
  ListOfPKFieldNo: List of [Integer]; 
  ListOfPkValuesAsTxt: List of [text];
begin
  //Get list of PK field numbers from RecordRef variable
  ListOfPKFieldNo := GetRecRefPKFieldNosInOrder(RecRef);

  //Check if field to change is part of Primary Key from RecordRef variable
  if not ListOfPKFieldNo.Contains(FieldNoToChange) then
    exit;

  //Let's say we have function to get all values as txt
  //Also, we get NEW value for required field
  //Attention! It's wrong approach because our fields can have any type
  //If you would like to see how avoid this problem, just take look for my Data Editor github project
  //https://github.com/Drakonian/data-editor-for-bc
  ListOfPkValuesAsTxt := GetListOfValuesFromFieldNumberListWithNew(RecRef, ListOfPKFieldNo, FieldNoToChange, NewValueAsVariant);

  //Now we have to call Rename() method to update required field from PK
  //But how? You need to pass all data in same order per one field LOL
  case ListOfPKFieldNo.Count() of
    1:
	  RecRef.Rename(ListOfPkValuesAsTxt.Get(1));
	2:
	  RecRef.Rename(ListOfPkValuesAsTxt.Get(1), ListOfPkValuesAsTxt.Get(2));
	3:
	  RecRef.Rename(ListOfPkValuesAsTxt.Get(1), ListOfPkValuesAsTxt.Get(2), ListOfPkValuesAsTxt.Get(3));
	//...
	//keep doing it for maximum number of fields in PK xD
	20:
	  RecRef.Rename(ListOfPkValuesAsTxt.Get(1), ListOfPkValuesAsTxt.Get(2), ListOfPkValuesAsTxt.Get(3),
					ListOfPkValuesAsTxt.Get(4), ListOfPkValuesAsTxt.Get(5), ListOfPkValuesAsTxt.Get(6),
					...
					ListOfPkValuesAsTxt.Get(20));
	end;
end;
Yes, there is a strange problem, in order to update values from PK for RecordRef we need to repeat the Rename() method for every possible number of fields in Primary Key with all combinations of types of fields. It's pretty funny :)

Suggested solution

I think you've already guessed what's missing from our puzzle. And we are missing two very important things:
      • Ability to transfer a list of values. There is no need to always pass each value separately. If you try to pass values with List data type you can even compile your code. But it won't work because the Rename function doesn't have an overload to accept lists. So, you will get runtime error.
      
      RecRef.Rename(ListOfValues);
      
      
      var
        ListOfVariant: list of [Variant];
      
      As a result, we can simply pass the list to the Rename() method
      
      RecRef.Rename(ListOfVariant);
      

      Summary

      We learned a few nuances of working with Rename(), and also noticed some strange things in the design of this method and the synergy with RecordRef. I also want to draw your attention to the fact that I now have a Twitter account. To subscribe, if you want to be informed about new blog post :)
      FRIDAY, December 24, 2021