Skip to main content
A newer version of this page is available. .
All docs
V22.1

Custom Aggregate Functions

  • 5 minutes to read

This topic describes how to implement a custom aggregate function and includes an example of the custom CountDistinct function.

Implement a Function

Define a class that implements the ICustomAggregate interface to create an aggregate that accepts a collection of values, uses an expression or multiple expressions to evaluate the values, and returns a single result.

To use a custom aggregate function in the Expression Editor, implement the ICustomAggregateBrowsable interface, which is an ICustomAggregate descendant. This interface allows you to add a function description displayed in the Expression Editor, and validate the number and type of operands.

Register a Function

Register at Runtime

To enable a custom aggregate function, register it in the application with the CriteriaOperator.RegisterCustomAggregate(ICustomAggregate) method at application startup. It makes the registered custom aggregate function available across the entire application in all locations where the CriteriaOperator is available.

The following code registers the CountDistinctCustomAggregate function:

DevExpress.Data.Filtering.CriteriaOperator.RegisterCustomAggregate(new CountDistinctCustomAggregate());

Register in the Visual Studio Report Designer

To enable a custom aggregate function in Visual Studio Report Designer, do the following:

  1. Create a custom component in Visual Studio. For this, press Ctrl+Shift+A, and select a new Component Class item.
  2. Call the CriteriaOperator.RegisterCustomAggregate(ICustomAggregate) method in the component’s constructor.
  3. Rebuild the project.
  4. Open the Report Designer and drop the newly created custom component from the Toolbox to the report design area.
  5. Invoke the Expression Editor. The previously registered function is available.

Unregister a Function

When a custom aggregate function is no longer needed or needs to be hidden from the user interface, you can remove it from the application using one of the methods below:

Unregistered functions are not available in the Expression Editor. An expression that has unregistered functions is evaluated as an empty value.

Function Call Syntax

To use a custom aggregate function, you can call it directly:

[CollectionProperty][].MyCustomAggregate(operands)

Alternatively, you can pass the custom aggregate function’s name as one of the parameters:

[CollectionProperty][].AGGREGATE('MyCustomAggregate', operands)

An aggregate is considered top-level if it is applied to the parent collection. If a custom aggregate is top-level, use the following syntax:

  • [].MyCustomAggregate(operands)

  • [].AGGREGATE('MyCustomAggregate', operands)

Example - CountDistinct Function

The following code implements the CountDistinct function that counts the number of distinct values in the specified collection or data field:

using DevExpress.Data.Filtering;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

class CountDistinctCustomAggregate : ICustomAggregateBrowsable
{
    static readonly CountDistinctCustomAggregate instance = new CountDistinctCustomAggregate();
    public static void Register() {
        CriteriaOperator.RegisterCustomAggregate(instance);
    }
    public static void Unregister() {
        CriteriaOperator.UnregisterCustomAggregate(instance);
    }
    public string Name { get { return nameof(CountDistinct); } }

    public int MinOperandCount => 1;

    public int MaxOperandCount => 1;

    public string Description => "This is a custom aggregate function";

    Type ICustomAggregate.ResultType(params Type[] operands) {
        return typeof(int);
    }
    object ICustomAggregate.CreateEvaluationContext() {
        return new HashSet<object>();
    }
    bool ICustomAggregate.Process(object context, object[] operands) {
        var ctx = (HashSet<object>)context;
        ctx.Add(operands[0]);
        return false;
    }
    object ICustomAggregate.GetResult(object context) {
        var ctx = (HashSet<object>)context;
        return ctx.Count;
    }

    public static object CountDistinct<T>(IEnumerable<T> collection, Expression<Func<T, object>> arg) {
        throw new InvalidOperationException("This method should not be called explicitly.");
    }

    public bool IsValidOperandCount(int count) {
        return true;
    }

    public bool IsValidOperandType(int operandIndex, int operandCount, Type type) {
        return true;
    }
} 

Register the CountDistinct function at application startup:

CountDistinctCustomAggregate.Register();

Run the application, open the Report Designer, and invoke the Expression Editor. You can use the CountDistinct function as shown in the following image:

Custom Aggregate Function in Expression Editor

View Example: Reporting for ASP.NET Core - How to Implement a Custom Function in the Expression Editor