Click or drag to resize

Computed Columns

[This is preliminary documentation and is subject to change.]

This topic contains the following sections:

Two things to make a column a computed column: A Function and Parameters.

Function

Adding a function to a column using BCF-Editor is simple. Just write the c#-code into Function-column in column grid.

Note Note
BCF-Editor supports autocomplete. Press (CONTROL + SPACE).
Examples:
  • (a + b) * 2
  • string.Join("-", itemNames ?? Enumerable.Empty<string>)
  • MyClass.MyStaticFunction(itemNames)
After doing so Error-Panel in BCF-Editor will show warnings like "Parameter 'itemNames' not defined". For parameter declaration see next section.
Note Note
Using class names as parameter names will work, but may cause wrong error messages.

Functions may also be added updating the BcfDataSetSetup.

Example
var columnSetup = (BcfColumnSetup<int>)myDataSetSetup.Tables[0].Columns[0];
columnSetup.Function = new BcfFunctionSetup<int>{
  Func = (parameters, row, rawArgs) => 0;
}

Parameters

The parameter grid is located below the column grid. Parameters are recognized by name. There are three parameter types you can define in BCF-Editor.

  • Linked Parameters:

    The "AccessPath" is a simple notation used to define cell-value access. To cause a parameter getting value from same row - simply set the column name. If value should be taken from a parent-row cell: set "-{RELATION_NAME}*.{COLUMN_NAME}." If value should be from a parent-parent row cell: set "-{RELATION_NAME1}*-{RELATION_NAME2}*.{COLUMN_NAME}". An access path always ends with a column name and can have none or multiple relation steps. Step separator is ".". Relation prefix specifies the direction the relation is accessed (seen from function) "-" means to less [or: collect value on parent row]; "+" means to many [or: collect values on child rows]. The relation suffix specifies whether to collect values from all or distinct rows ("*" means all rows; "%" means distinct rows).

    Tip Tip
    Use BCF-Editors "Access Path Editor" to link cells (available via parameter grids "Link Parameter" command).
    Parameter access via relations may cause parameter type adjustment. Linking a parameter to a parent row column of type int results in parameter type int?. Linking to child rows column of type int results in IEnumerable<int>. The commands "T!", "T?", "T" and "T[]" may help to adjust parameter type. When taking values via multiple child-relations the values will be concatenated; e.g. access via "+DocumentToChapter*.+ChapterToParagraph*.Text" results in IEnumerable<string> (NOT IEnumerable<IEnumerable<string>>). When a parameter has a child-step in path its value is always not null. It will return an empty enumerable (or array).

    Linking parameters this way is recommended. BcfTransaction flags the computed cell as dirty when a linked cell is updated or relation changes. The computed cell will be recalculated implicit on BcfTransactionCommit or explicit on BcfTransactionCompute.

    When linking parameters you may encounter circular references:

    • Not preventable

      When ColumnA depends on ColumnB and vice versa, BCF-Editor shows an error and code generator is disabled.

    • Preventable

      When having a relation with both ends on same table (self-join, tree) the column "Level" may be computed by parentLevel.GetValueOrDefault(-1) + 1 (parameter: Name = "parentLevel"; AccessPath = "-NodeToChild*.Level"). At runtime this will work until a row is its own parent row. In that case an BcfCalculationException will be thrown.

      When BCF-Editor detects a preventable circular reference it shows a warning. To suppress the warning check "Suppress" in parameter grid.

    • Self Reference

      When using computed column name as parameters AccessPath you have a self reference. This causes parameter to return the cell value before calculation starts. To suppress BCF-Editor warnings use "previousValue" as parameter name.

      Self references were used before BCF 4.0 to perform custom equality comparison. When computed value equals cell value, returning the cell value causes a performance benefit. Since BCF 4.0 it is recommended to use custom EqualityComparers on column EqualityComparer.

      Caution note Caution
      The "Self Reference" feature may be removed in future releases.
      If you really need the previous cell value in calculation, it is recommended to use a parameter function instead (e.g. column(Type = List<int>) (fn, row) => row.GetCell((BcfColumn<List<int>>)fn.Column).Value).

  • Value Parameters

    A function (Func<BcfFunction, BcfRow, TValue>) in "ValueFunc" creates a parameter using a function as source. For example use (fn, row) => ((MyColumnOptions)fn.Column.Options).MyMethod() to access "MyMethod" of columns Options".

  • Value Function Parameters

    A value in "Value" will create a parameter using its Value property as source. To change value at runtime use MyColumn.Function.Parameters.Array.OfType<BcfCustomValueParameter<int>>().First(p => p.Name == "MyParameterName").Value = 42;.

    Setting Value property of the parameter will not force recalculation.

    Note Note
    Call Invalidate to cause recalculation.

With BCF 4.0 adding parameters in code became a bit tricky. If you have to, examine generated DataSetSetup code first and refer chapter "adding rules at runtime".

Hacking Functions

Sometimes when creating a new version with function update you wish the cell keeps or accepts old value. With BCF 4.0 there are some new options to do this. Computed cells are not longer readonly. But setting value of a computed cell will flag the cell as dirty. Since the cell is recomputed when transaction commits, keep transaction open or call BcfTransactionAcceptValuesAsResults.

Caution note Caution
BcfTransactionAcceptValuesAsResults makes the transaction "forgets" recalculation. To "repair" later call myBcfDataSet.BeginTransaction().EnterDumbMode().Commit();.

How CalculationWorks Calculation Works

The BcfTransaction manages a list containing references to 'dirty' cells. On Compute the references are copied into a calculation plan ordered by the cells "calculation plan bucket index". The index is computed as "MAX(cells-depending-on-this-cell.calculation-plan-bucket-index) + 1". The calculation starts with cells having the highest index. When a cell-value is computed and updated, all direct depending cells are added to calculation plan. This workflow guarantees computed cells are recalculated once maximum.

Immediately after calculation the calculation plan is destroyed. To investigate the plan override BcfDataSetOnBeginCompute and OnEndCompute and get a snapshot of the calculation plan using BcfDataSetCurrentCalculationPlan.

The cells in one calculation plan bucket are definitively independent from each other and can be computed in parallel. But managing tasks causes an overhead; so only if there are multiple IO-bound or heavy single threaded CPU-bound calculations are in same bucket, parallel calculation is faster. To enable parallel calculation see ParallelOptions. To control function parallel execution see: Relation Column: Settings/Async Option and BcfCalculatorAsyncOption.

See Also