xtrareports-403889-feature-guide-to-devexpress-reports-use-expressions-custom-aggregate-functions.md
This topic describes how to implement a custom aggregate function and includes an example of the custom CountDistinct 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.
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());
DevExpress.Data.Filtering.CriteriaOperator.RegisterCustomAggregate(New CountDistinctCustomAggregate())
Place an attribute on a custom function to enable it in the Visual Studio Report Designer. Use the following attribute:
VSDesignerCustomFunctionAttributeRegisters a custom function in the Visual Studio Report Designer. You can specify the VSDesignerCustomFunctionScope attribute argument to define the registration scope in the Visual Studio Report Designer.
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.
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)
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;
public 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;
}
}
Imports System.Linq.Expressions
Imports DevExpress.Data.Filtering
Public Class CountDistinctCustomAggregate
Implements ICustomAggregateFormattable, ICustomAggregateBrowsable
Private Shared ReadOnly instance As New CountDistinctCustomAggregate()
Public Shared Sub Register()
CriteriaOperator.RegisterCustomAggregate(instance)
End Sub
Public Shared Sub Unregister()
CriteriaOperator.UnregisterCustomAggregate(instance)
End Sub
Public ReadOnly Property Name() As String Implements ICustomAggregate.Name
Get
Return NameOf(CountDistinct)
End Get
End Property
Public ReadOnly Property MinOperandCount() As Integer Implements ICustomAggregateBrowsable.MinOperandCount
Get
Return 1
End Get
End Property
Public ReadOnly Property MaxOperandCount() As Integer Implements ICustomAggregateBrowsable.MaxOperandCount
Get
Return 1
End Get
End Property
Public ReadOnly Property Description() As String Implements ICustomAggregateBrowsable.Description
Get
Return "This is a custom aggregate function"
End Get
End Property
Private Function ICustomAggregate_ResultType(ParamArray ByVal operands() As Type) As Type Implements ICustomAggregate.ResultType
Return GetType(Integer)
End Function
Private Function ICustomAggregate_CreateEvaluationContext() As Object Implements ICustomAggregate.CreateEvaluationContext
Return New HashSet(Of Object)()
End Function
Private Function ICustomAggregate_Process(ByVal context As Object, ByVal operands() As Object) As Boolean Implements ICustomAggregate.Process
Dim ctx = DirectCast(context, HashSet(Of Object))
ctx.Add(operands(0))
Return False
End Function
Private Function ICustomAggregate_GetResult(ByVal context As Object) As Object Implements ICustomAggregate.GetResult
Dim ctx = DirectCast(context, HashSet(Of Object))
Return ctx.Count
End Function
Private Function ICustomAggregateFormattable_Format(ByVal providerType As Type, ParamArray ByVal operands() As String) As String Implements ICustomAggregateFormattable.Format
Return String.Format("COUNT(distinct {0})", operands(0))
End Function
Public Shared Function CountDistinct(Of T)(ByVal collection As IEnumerable(Of T), ByVal arg As Expression(Of Func(Of T, Object))) As Object
Throw New InvalidOperationException("This method should not be called explicitly.")
End Function
Public Function IsValidOperandCount(ByVal count As Integer) As Boolean Implements ICustomAggregateBrowsable.IsValidOperandCount
Return True
End Function
Public Function IsValidOperandType(ByVal operandIndex As Integer, ByVal operandCount As Integer, ByVal type As Type) As Boolean Implements ICustomAggregateBrowsable.IsValidOperandType
Return True
End Function
End Class
Register the CountDistinct function at application startup:
CountDistinctCustomAggregate.Register();
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: