Skip to main content
.NET Standard 2.0+

How to: Implement and Use Custom Aggregate Functions

  • 5 minutes to read

This article describes how to implement and use a custom aggregate function that counts distinct items. This article assumes that you have a WinForms application with the Customer and Order business classes.


You can download a complete example here: How to Implement Custom Aggregates

Implement a Custom Aggregate Function

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

namespace XpoCustomAggregate {
    class CountDistinctCustomAggregate : ICustomAggregate, ICustomAggregateQueryable, ICustomAggregateFormattable {

  • In the CountDistinctCustomAggregate class, implement the properties and methods as the code below demonstrates. Note that the Name property value cannot match aggregate function names in the application and does not allow standard aggregate names, for example, Min, Max, Sum, Avg, Single, Exists, and Count.
static readonly CountDistinctCustomAggregate instance = new CountDistinctCustomAggregate();
public static void Register() {
public static void Unregister() {
public string Name { get { return nameof(CountDistinct); } }
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;
    return false;
object ICustomAggregate.GetResult(object context) {
    var ctx = (HashSet<object>)context;
    return ctx.Count;
string ICustomAggregateFormattable.Format(Type providerType, params string[] operands) {
    return string.Format("COUNT(distinct {0})", operands[0]);
MethodInfo ICustomAggregateQueryable.GetMethodInfo() {
    return typeof(CountDistinctCustomAggregate).GetMethod(Name);
public static object CountDistinct<T>(IEnumerable<T> collection, Expression<Func<T, object>> arg) {
    throw new InvalidOperationException("This method should not be called explicitly.");
Show the API description




Registers a custom aggregate function to use in any CriteriaOperator-based criteria in your application.


Unregisters a specified custom aggregate function from use in any CriteriaOperator-based criteria in your application.


When implemented by a custom aggregate function, specifies its name.


When implememnted by a custom aggregate function, determines its return value type based on the type of aggregate function parameters.

Process(Object, Object[])

Is called to process every element of a collection supplied to a custom aggregate function.


Gets a custom aggregate function’s result.

Format(Type, String[])

Builds a SQL command that calculates a custom aggregate function result on the server side.


When implemented by a class, returns the metadata of a method associated with a custom aggregate function used in LINQ to XPO expressions.

Add a Grid Control and Register the Custom Aggregate Function

  • Add a new GridControl to the form.
  • Open the form’s code file and register the custom aggregate.

Choose the Data Source

There are two ways you can load data from the data store. If you work with the CriteriaOperator, you can use the XPView, XPCollection, or other XPO data sources. Use XPQuery if you work with LINQ.

Use the XPView as a Data Source

  • In the form’s file, initialize a new XPView.
  • Add the required properties to the XPView and assign the XPView to the GridControl‘s DataSource.
XPView xpView = new XPView(session1, typeof(Customer));
xpView.AddProperty("ContactName", CriteriaOperator.Parse("[FirstName] + ' ' + [LastName]"));
xpView.AddProperty("DistinctProducts", CriteriaOperator.Parse("[Orders][].CountDistinct([ProductName])"));
xpView.Sorting.Add(new SortProperty("ContactName", DevExpress.Xpo.DB.SortingDirection.Ascending));
gridControl1.DataSource = xpView;

Use XPQuery as a Data Source

gridControl2.DataSource = new XPQuery<Customer>(session1)
    .Select(t => new {
        ContactName = t.ContactName,
        DistinctProducts = (int)CountDistinctCustomAggregate.CountDistinct(t.Orders, o => o.ProductName),
    .OrderBy(t => t.ContactName)