dashboard-403419-common-features-advanced-analytics-aggregations-custom-aggregate-functions.md
The Dashboard controls aggregate data when you construct a calculated field expression. This allows you to evaluate calculated fields on a summary level. Aggregated functions serve to create aggregated expressions. Along with the predefined aggregations (like Min, Max, Sum, Avg), the Dashboard supports custom aggregation functions. You should create and register a custom function to implement it into your project.
Review the common and platform-specific sections to implement a custom aggregate function.
These steps are required for all platforms:
Define a class that implements the ICustomAggregateFunction interface to create an aggregate that accepts a collection of values, uses an expression or several expressions to evaluate the values, and returns the result.
Implement the ICustomFunctionOperatorBrowsable interface to validate a custom function and supply additional information (a function’s category, function’s description, parameter count, and so on) on a custom function for the Expression Editor.
Define a class that implements the ICustomAggregateFunctionContext<TInput, TOutput> interface. The class implements the logic of a custom function.
For the WinForms Designer, you can additionally implement the ICustomFunctionCategory interface to define a category under which the Expression Editor should display the custom function. Otherwise, the custom function is displayed in the “String” function’s category.
The following code snippet adds StringConcat function in the “Aggregate” function’s category:
using DevExpress.DataAccess.ExpressionEditor;
namespace Dashboard_StringConcatAggregate {
class StringConcatFunction : ICustomAggregateFunction, ICustomFunctionOperatorBrowsable, ICustomFunctionCategory {
public string Name => "StringConcat";
//...
public string FunctionCategory => "Aggregate";
//...
}
}
Imports DevExpress.DataAccess.ExpressionEditor
Namespace Dashboard_StringConcatAggregate
Friend Class StringConcatFunction
Implements ICustomAggregateFunction, ICustomFunctionOperatorBrowsable, ICustomFunctionCategory
Public ReadOnly Property Name() As String Implements DevExpress.Data.Filtering.ICustomFunctionOperator.Name
Get
Return "StringConcat"
End Get
End Property
'...
Private ReadOnly Property ICustomFunctionCategory_FunctionCategory As String Implements ICustomFunctionCategory.FunctionCategory
Get
Return "Aggregate"
End Get
End Property
'...
End Class
End Namespace
After you registered the function, it appears in the Expression Editor:
The Expression Editor does not display registered custom functions in the Web Control. But you can still use these custom functions to construct calculated fields and expressions without intelligent code completion (suggesting functions as you type).
In Server mode, implement the ICustomFunctionOperatorFormattable interface to provide a database-specific SQL command for your custom function. Use the ICustomFunctionOperatorFormattable.Format method to return a database-specific SQL command which substitutes a custom function’s calls in query statements.
The following code snippet shows how to implement ICustomFunctionOperatorFormattable.Format for the FirstValue function in server mode:
using DevExpress.DataAccess.ExpressionEditor;
namespace Dashboard_StringConcatAggregate {
class FirstValueAggregateFunction : ICustomFunctionOperatorFormattable {
//...
public string Format(Type providerType, params string[] operands) {
return string.Format("FIRST_VALUE({0})", operands[0]);
}
//...
}
}
Imports DevExpress.DataAccess.ExpressionEditor
Namespace Dashboard_FirstValueAggregate
Friend Class FirstValueAggregateFunction
Implements ICustomFunctionOperatorFormattable
'...
Public Function Format(ByVal providerType As Type, ParamArray ByVal operands() As String) As String Implements ICustomFunctionOperatorFormattable.Format
Return String.Format("FIRST_VALUE({0})", operands(0))
End Function
'...
End Class
End Namespace
Call the CriteriaOperator.RegisterCustomFunction method at the application startup to register a custom function in your project.
CriteriaOperator.RegisterCustomFunction(new StringConcatFunction());
CriteriaOperator.RegisterCustomFunction(New StringConcatFunction())
To unregister a custom function, call the CriteriaOperator.UnregisterCustomFunction method.
The following example shows how to implement a custom function that uses string concatenation to aggregate data in client mode. In this example, the Grid dashboard item displays ContactName values concatenated by country.
View Example: Dashboard for WinForms - How to Aggregate Data by String Concatenation
using DevExpress.Data.Filtering;
using DevExpress.DataAccess.ExpressionEditor;
using DevExpress.DataProcessing.Criteria;
using System;
using System.Collections.Generic;
namespace Dashboard_StringConcatAggregate {
class StringConcatFunction : ICustomAggregateFunction, ICustomFunctionOperatorBrowsable,
ICustomFunctionCategory {
public string Name => "StringConcat";
public int MinOperandCount => 1;
public int MaxOperandCount => 1;
public string Description => @"Takes strings, aggregates by input value,
and displays them separated by commas.";
public FunctionCategory Category => DevExpress.Data.Filtering.FunctionCategory.Text;
public string FunctionCategory => "Aggregate";
public object Evaluate(params object[] operands) {
throw new NotImplementedException();
}
public Type GetAggregationContextType(Type inputType) {
return typeof(StringConcatState);
}
public bool IsValidOperandCount(int count) {
return count <= MaxOperandCount && count >= MinOperandCount;
}
public bool IsValidOperandType(int operandIndex, int operandCount, Type type) {
return IsValidOperandCount(operandCount) && operandIndex == 0 && type == typeof(string);
}
public Type ResultType(params Type[] operands) {
return typeof(string);
}
}
class StringConcatState : ICustomAggregateFunctionContext<string, string> {
List<string> enumeration = new List<string>();
ISet<string> names = new HashSet<string>();
public string GetResult() {
return String.Join(", ", enumeration.ToArray());
}
public void Process(string value) {
if (names.Add(value)) {
enumeration.Add(value);
}
}
}
}
Imports DevExpress.Data.Filtering
Imports DevExpress.DataAccess.ExpressionEditor
Imports DevExpress.DataProcessing.Criteria
Namespace Dashboard_StringConcatAggregate
Friend Class StringConcatFunction
Implements ICustomAggregateFunction, ICustomFunctionOperatorBrowsable,
ICustomFunctionCategory
Public ReadOnly Property Name() As String Implements DevExpress.Data.Filtering.ICustomFunctionOperator.Name
Get
Return "StringConcat"
End Get
End Property
Public ReadOnly Property MinOperandCount() As Integer Implements ICustomFunctionOperatorBrowsable.MinOperandCount
Get
Return 1
End Get
End Property
Public ReadOnly Property MaxOperandCount() As Integer Implements ICustomFunctionOperatorBrowsable.MaxOperandCount
Get
Return 1
End Get
End Property
Public ReadOnly Property Description() As String Implements ICustomFunctionOperatorBrowsable.Description
Get
Return "Takes strings, aggregates by input value, and displays them separated by commas."
End Get
End Property
Public ReadOnly Property Category() As FunctionCategory Implements ICustomFunctionOperatorBrowsable.Category
Get
Return DevExpress.Data.Filtering.FunctionCategory.Text
End Get
End Property
Private ReadOnly Property ICustomFunctionCategory_FunctionCategory As String Implements ICustomFunctionCategory.FunctionCategory
Get
Return "Aggregate"
End Get
End Property
Public Function Evaluate(ParamArray ByVal operands() As Object) As Object Implements DevExpress.Data.Filtering.ICustomFunctionOperator.Evaluate
Throw New NotImplementedException()
End Function
Public Function IsValidOperandCount(ByVal count As Integer) As Boolean Implements ICustomFunctionOperatorBrowsable.IsValidOperandCount
Return count <= MaxOperandCount AndAlso count >= MinOperandCount
End Function
Public Function IsValidOperandType(ByVal operandIndex As Integer, ByVal operandCount As Integer, ByVal type As Type) As Boolean Implements ICustomFunctionOperatorBrowsable.IsValidOperandType
Return IsValidOperandCount(operandCount) AndAlso operandIndex = 0 AndAlso type Is GetType(String)
End Function
Public Function ResultType(ParamArray ByVal operands() As Type) As Type Implements DevExpress.Data.Filtering.ICustomFunctionOperator.ResultType
Return GetType(String)
End Function
Private Function GetAggregationContextType(inputType As Type) As Type Implements ICustomAggregateFunction.GetAggregationContextType
Return GetType(StringConcatState)
End Function
End Class
Friend Class StringConcatState
Implements ICustomAggregateFunctionContext(Of String, String)
Private enumeration As New List(Of String)()
Private names As ISet(Of String) = New HashSet(Of String)()
Public Function GetResult() As String Implements ICustomAggregateFunctionContext(Of String, String).GetResult
Return String.Join(", ", enumeration.ToArray())
End Function
Public Sub Process(ByVal value As String) Implements ICustomAggregateFunctionContext(Of String, String).Process
If names.Add(value) Then
enumeration.Add(value)
End If
End Sub
End Class
End Namespace
using System;
using System.Windows.Forms;
using DevExpress.Data.Filtering;
// ...
namespace Dashboard_StringConcatAggregate {
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
// ...
CriteriaOperator.RegisterCustomFunction(new StringConcatFunction());
Application.Run(new Form1());
}
}
}
Imports System
Imports System.Windows.Forms
Imports DevExpress.Data.Filtering
' ...
Namespace Dashboard_StringConcatAggregate
Friend Module Program
''' <summary>
''' The main entry point for the application.
''' </summary>
<STAThread>
Sub Main()
' ...
CriteriaOperator.RegisterCustomFunction(New StringConcatFunction())
Application.Run(New Form1())
End Sub
End Module
End Namespace
The following code snippet shows how to aggregate data by the field’s first value in server mode. In this example, the Grid dashboard item displays the first ContactName value by country.
View Example: Dashboard for WinForms - How to Aggregate Data by the Field's First Value
using DevExpress.Data.Filtering;
using DevExpress.DataAccess.ExpressionEditor;
using DevExpress.DataProcessing.Criteria;
using System;
using System.Collections.Generic;
namespace Dashboard_FirstValueAggregate {
class FirstValueAggregateFunction : ICustomAggregateFunction, ICustomFunctionOperatorBrowsable,
ICustomFunctionCategory, ICustomFunctionOperatorFormattable {
public string Name => "FirstValue";
public int MinOperandCount => 1;
public int MaxOperandCount => 1;
public string Description => @"Aggregates data by input value,
and displays the first value of the field";
public FunctionCategory Category => DevExpress.Data.Filtering.FunctionCategory.Text;
public string FunctionCategory => "Aggregate";
public object Evaluate(params object[] operands) {
throw new NotImplementedException();
}
public string Format(Type providerType, params string[] operands) {
return string.Format("FIRST_VALUE({0})", operands[0]);
}
public Type GetAggregationContextType(Type inputType) {
return typeof(FirstValueAggregateState<>).MakeGenericType(inputType);
}
public bool IsValidOperandCount(int count) {
return count <= MaxOperandCount && count >= MinOperandCount;
}
public bool IsValidOperandType(int operandIndex, int operandCount, Type type) {
return IsValidOperandCount(operandCount) && operandIndex == 0;
}
public Type ResultType(params Type[] operands) {
return operands[0];
}
}
class FirstValueAggregateState<TInput> : ICustomAggregateFunctionContext<TInput, TInput> {
bool isSet = false;
TInput firstValue;
public TInput GetResult() {
return isSet ? firstValue : default(TInput);
}
public void Process(TInput value) {
if(!isSet) {
firstValue = value;
isSet = true;
}
}
}
}
Imports DevExpress.Data.Filtering
Imports DevExpress.DataAccess.ExpressionEditor
Imports DevExpress.DataProcessing.Criteria
Imports System
Imports System.Collections.Generic
Namespace Dashboard_FirstValueAggregate
Friend Class FirstValueAggregateFunction
Implements ICustomAggregateFunction, ICustomFunctionOperatorBrowsable,
ICustomFunctionCategory, ICustomFunctionOperatorFormattable
Public ReadOnly Property Name() As String Implements DevExpress.Data.Filtering.ICustomFunctionOperator.Name
Get
Return "FirstValue"
End Get
End Property
Public ReadOnly Property MinOperandCount() As Integer Implements ICustomFunctionOperatorBrowsable.MinOperandCount
Get
Return 1
End Get
End Property
Public ReadOnly Property MaxOperandCount() As Integer Implements ICustomFunctionOperatorBrowsable.MaxOperandCount
Get
Return 1
End Get
End Property
Public ReadOnly Property Description() As String Implements ICustomFunctionOperatorBrowsable.Description
Get
Return "Aggregates data by input value, and displays the first value of the field"
End Get
End Property
Public ReadOnly Property Category() As FunctionCategory Implements ICustomFunctionOperatorBrowsable.Category
Get
Return DevExpress.Data.Filtering.FunctionCategory.Text
End Get
End Property
Public ReadOnly Property FunctionCategory() As String Implements ICustomFunctionCategory.FunctionCategory
Get
Return "Aggregate"
End Get
End Property
Public Function Evaluate(ParamArray ByVal operands() As Object) As Object Implements DevExpress.Data.Filtering.ICustomFunctionOperator.Evaluate
Throw New NotImplementedException()
End Function
Public Function Format(ByVal providerType As Type, ParamArray ByVal operands() As String) As String Implements ICustomFunctionOperatorFormattable.Format
Return String.Format("FIRST_VALUE({0})", operands(0))
End Function
Public Function GetAggregationContextType(ByVal inputType As Type) As Type Implements ICustomAggregateFunction.GetAggregationContextType
Return GetType(FirstValueAggregateState(Of )).MakeGenericType(inputType)
End Function
Public Function IsValidOperandCount(ByVal count As Integer) As Boolean Implements ICustomFunctionOperatorBrowsable.IsValidOperandCount
Return count <= MaxOperandCount AndAlso count >= MinOperandCount
End Function
Public Function IsValidOperandType(ByVal operandIndex As Integer, ByVal operandCount As Integer, ByVal type As Type) As Boolean Implements ICustomFunctionOperatorBrowsable.IsValidOperandType
Return IsValidOperandCount(operandCount) AndAlso operandIndex = 0
End Function
Public Function ResultType(ParamArray ByVal operands() As Type) As Type Implements DevExpress.Data.Filtering.ICustomFunctionOperator.ResultType
Return operands(0)
End Function
End Class
Friend Class FirstValueAggregateState(Of TInput)
Implements ICustomAggregateFunctionContext(Of TInput, TInput)
Private isSet As Boolean = False
Private firstValue As TInput
Public Sub Process(ByVal value As TInput) Implements ICustomAggregateFunctionContext(Of TInput, TInput).Process
If Not isSet Then
firstValue = value
isSet = True
End If
End Sub
Private Function GetResult() As TInput Implements ICustomAggregateFunctionContext(Of TInput, TInput).GetResult
Return If(isSet, firstValue, Nothing)
End Function
End Class
End Namespace
using DevExpress.Data.Filtering;
using System;
using System.Windows.Forms;
// ...
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
// ...
CriteriaOperator.RegisterCustomFunction(new FirstValueAggregateFunction());
Application.Run(new Form1());
}
}
}
Imports DevExpress.Data.Filtering
Imports System
Imports System.Windows.Forms
' ...
Namespace Dashboard_FirstValueAggregate
Friend Module Program
''' <summary>
''' The main entry point for the application.
''' </summary>
<STAThread>
Sub Main()
' ...
CriteriaOperator.RegisterCustomFunction(New FirstValueAggregateFunction())
Application.Run(New Form1())
End Sub
End Module
End Namespace
See Also