Use SetAutoCalcFields for loops

Properties
PC0035 Warning Performance Code Fix Ignore Obsolete

When CalcFields is called inside a loop, each iteration issues a separate SQL query per FlowField. For a table with 10,000 records and 3 FlowFields, that is 30,000 additional round trips on top of the data retrieval itself. SetAutoCalcFields called before the loop tells the runtime to include the FlowField calculations in the main SELECT query, eliminating the per-iteration overhead entirely.

Example

The following code calls CalcFields on every iteration of a FindSet loop:

procedure MyProcedure()
var
    Customer: Record Customer;
begin
    Customer.FindSet();
    repeat
        Customer.CalcFields("Balance (LCY)"); // Use SetAutoCalcFields for loops [PC0035]
    until Customer.Next() = 0;
end;

To fix this, replace the CalcFields call with SetAutoCalcFields before the loop:

procedure MyProcedure()
var
    Customer: Record Customer;
begin
    Customer.SetAutoCalcFields("Balance (LCY)");
    Customer.FindSet();
    repeat
        // Balance (LCY) is now automatically calculated on each record fetch
    until Customer.Next() = 0;
end;

Reports

A report dataitem loops over its records, so OnAfterGetRecord runs once per row. A CalcFields call there issues one SQL query per FlowField on every row, the same per-iteration cost as a repeat loop:

report 50100 "Customer Balances"
{
    dataset
    {
        dataitem(Customer; Customer)
        {
            column(Balance; "Balance (LCY)") { }

            trigger OnAfterGetRecord()
            begin
                CalcFields("Balance (LCY)"); // Use SetAutoCalcFields for loops [PC0035]
            end;
        }
    }
}

Move the calculation to SetAutoCalcFields in OnPreDataItem. It runs once, before the dataitem iterates, and includes the FlowField in the main query:

report 50100 "Customer Balances"
{
    dataset
    {
        dataitem(Customer; Customer)
        {
            column(Balance; "Balance (LCY)") { }

            trigger OnPreDataItem()
            begin
                SetAutoCalcFields("Balance (LCY)");
            end;
        }
    }
}

When the diagnostic is reported

The rule fires when all of the following conditions are true:

  • A CalcFields invocation is found inside a loop body
  • The CalcFields call is on the same record variable that drives the loop
  • The loop is one of: FindSet/Find + repeat...until, while...do, or a report OnAfterGetRecord trigger

The rule does not track CalcFields calls across method boundaries — a CalcFields inside a helper procedure called from a loop is not detected.

The rule is not reported on temporary records, because SetAutoCalcFields rewrites the SQL SELECT and is a no-op for in-memory data. This covers both forms of temporary: a variable declared with the temporary keyword (Record Customer temporary) and a table object declared with TableType = Temporary. CalcFields still works on such records (it evaluates each FlowField’s CalcFormula against the real source tables), so no swap is suggested.

Code fix

The ALCops: Use SetAutoCalcFields before loop code fix applies to FindSet/Find + repeat...until and while...do loops:

  1. Inserts a SetAutoCalcFields call with the same field arguments before the loop (or before the FindSet/Find statement)
  2. Removes the CalcFields call from inside the loop body

When multiple CalcFields calls exist for the same variable in the same loop, each is handled individually by the fix-all provider.

The fix is not offered for report OnAfterGetRecord triggers. There is no loop statement to insert before, and the correct location is OnPreDataItem, a different trigger. Move SetAutoCalcFields there manually, as shown above.

See also