Back to Devexpress

Custom Aggregate Functions

dashboard-403419-common-features-advanced-analytics-aggregations-custom-aggregate-functions.md

latest21.1 KB
Original Source

Custom Aggregate Functions

  • Aug 05, 2025
  • 9 minutes to read

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.

Create a Custom Aggregate Function

Review the common and platform-specific sections to implement a custom aggregate function.

Common Steps

These steps are required for all platforms:

  1. 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.

  2. 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.

  3. Define a class that implements the ICustomAggregateFunctionContext<TInput, TOutput> interface. The class implements the logic of a custom function.

WinForms Specific

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:

csharp
using DevExpress.DataAccess.ExpressionEditor;

namespace Dashboard_StringConcatAggregate {
    class StringConcatFunction : ICustomAggregateFunction, ICustomFunctionOperatorBrowsable, ICustomFunctionCategory {
        public string Name => "StringConcat";
        //...
        public string FunctionCategory => "Aggregate";
        //...
    }
}
vb
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:

Web Specific

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).

Server Mode Specific

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:

csharp
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]);
        }
        //...
    }
}
vb
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

Register a Custom Aggregate Function

Call the CriteriaOperator.RegisterCustomFunction method at the application startup to register a custom function in your project.

csharp
CriteriaOperator.RegisterCustomFunction(new StringConcatFunction());
vb
CriteriaOperator.RegisterCustomFunction(New StringConcatFunction())

To unregister a custom function, call the CriteriaOperator.UnregisterCustomFunction method.

Examples

How to Aggregate Data by String Concatenation in Client Mode

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

csharp
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);
            }
        }
    }
}
vb
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
csharp
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());      
        }
    }
}
vb
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

How to Aggregate Data by the Field’s First Value in Server Mode

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

cs
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;
            }
        }
    }
}
cs
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
csharp
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());      
        }
    }
}
vb
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

Expression Constants, Operators, and Functions