Custom aggregate function C# PivotData Examples
This article explains how to define and use custom summary function with NReco.PivotData OLAP library.
Aggregator interfaces
PivotData library defines 2 interfaces for incapsulating summary function logic:
- IAggregator: represents accumulator: holds current value + how to calculate it by input data
-
IAggregatorFactory: represents a factory for creating
IAggregator
instances
Aggregator factory should implement Create method with 2 overloads:
IAggregator Create()
: creates 'empty' aggregatorIAggregator Create(object state)
: creates aggregator initialized with specified state object
Aggregator should implement the following methods/properties:
void Push(object v, Func<Object, string, Object> getValue)
: accumulate input data. Field values may be accessed with help of getValue delegate.void Merge(IAggregator aggr)
: include accumulated data from specified aggregator. This operation should be defined only for aggregators of the same type.object GetState()
: get serializable structure that represents accumulated data.uint Count { get; }
: number of input values accumulated by the aggregator.object Value { get; }
: object that represents accumulated value (in most cases this is a number - integer or decimal).
Implementation example sum function
SumAggregator -- incapsulates summary function logic (how to accumulate values, how to merge 2 aggregators):public class SumAggregator : IAggregator { decimal total = 0; // default value for empty aggregator uint count = 0; // default value for empty aggregator string field; public SumAggregator(string f) { field = f; } public SumAggregator(string f, object state) : this(f) { // this constructor is used in SumAggregatorFactory.Create(object) var stateArr = state as object[]; if (stateArr==null || stateArr.Length!=2) throw new InvalidOperationException("invalid state"); count = Convert.ToUInt32(stateArr[0]); total = Convert.ToDecimal(stateArr[1]); } public void Push(object r, Func<object,string,object> getValue) { var v = ConvertHelper.ConvertToDecimal(getValue(r,field), Decimal.MinValue); if (v!=Decimal.MinValue) { // accumulate new value total += v; count++; } } public object Value { get { return total; } } public uint Count { get { return count; } } public void Merge(IAggregator aggr) { var sumAggr = aggr as SumAggregator; if (sumAggr==null) throw new ArgumentException("aggr"); // combine accumulated values from 2 aggregators count += sumAggr.count; total += sumAggr.total; } public object GetState() { // aggregator state: compact serializable structure return new object[]{count, total}; } }SumAggregatorFactory -- implementation is rather trivial:
public class SumAggregatorFactory : IAggregatorFactory { public string Field { get; private set; } public SumAggregatorFactory(string field) { Field = field; } public IAggregator Create() { return new SumAggregator(Field); } public IAggregator Create(object state) { return new SumAggregator(Field, state); } public override bool Equals(object obj) { var aggrFactory = obj as SumAggregatorFactory; if (aggrFactory==null) return false; return aggrFactory.fld==fld; } public override string ToString() { return String.Format("Sum of {0}", Field); } }That's all.
Register custom aggregator in PivotDataFactory
If you want to use your custom aggregator
in reports created with web pivot builder (ToolkitPivotBuilderMvc example)
it should be registered in
PivotDataFactory
component with RegisterAggregator
method:
var pivotDataFactory = new PivotDataFactory(); pivotDataFactory.RegisterAggregator( // unique aggregator name across the factory "Sum", // type of aggregator factory component typeof(SumAggregatorFactory), // delegate that creates aggr factory instance (aggrType, aggrParams) => new SumAggregatorFactory((string)aggrParams[0]), // delegate that returns aggr factory parameters (aggr) => new object[] { ((SumAggregator)aggr).Field } );