One of the exciting changes that we are looking forward to shipping in 11.1, is the global registration of custom functions. Once registered, a custom function can be used where ever CriteriaOperators are processed, as well as being available in all Expression Editors.
So how does it work? Simply, a custom function is a class which implements several interfaces:
- ICustomFunctionOperator – is responsible for base functionality such as function name, result type and client implementation of the function.
- ICustomFunctionOperatorFormattable – is responsible for the formatting (implementation) of the function on the server (database) side.
- ICustomFunctionOperatorBrowsable - provides additional information for Expression Editors such as function category, function description, argument count etc.
ICustomFunctionOperatorFormattable and ICustomFunctionOperatorBrowsable inherit from ICustomFunctionOperator. So, if you implement either of first two interfaces, there is no need to implement the last.
Knowing this, let’s write our own custom function to handle concatenation, let’s call it “MyConcat”
using DevExpress.Data.Filtering;
using DevExpress.Xpo.DB;
using System.Resources;
using CustomFunctionsExample.Properties;
public class MyConcatFunction : ICustomFunctionOperatorBrowsable,
ICustomFunctionOperatorFormattable {
const string FunctionName = "MyConcat";
#region ICustomFunctionOperatorBrowsable Members
static readonly MyConcatFunction instance = new MyConcatFunction();
public static void Register() {
CriteriaOperator.RegisterCustomFunction(instance);
}
public static bool Unregister() {
return CriteriaOperator.UnregisterCustomFunction(instance);
}
public FunctionCategory Category {
get { return FunctionCategory.Text; }
}
public string Description {
get { return Resources.MyConcatDescription; }
}
public bool IsValidOperandCount(int count) {
// At least two operands must be specified.
return count > 1;
}
public bool IsValidOperandType(int operandIndex, int operandCount, Type type) {
// All operands should be strings.
return type == typeof(string);
}
public int MaxOperandCount {
//Accepts an infinite number of operands.
get { return -1; }
}
public int MinOperandCount {
// At least two operands must be specified.
get { return 2; }
}
#endregion
#region ICustomFunctionOperator Members
// Evaluates the function on the client.
public object Evaluate(params object[] operands) {
StringBuilder result = new StringBuilder();
foreach(string operand in operands) {
result.Append(operand);
}
return result.ToString();
}
public string Name {
get { return FunctionName; }
}
public Type ResultType(params Type[] operands) {
foreach(Type operand in operands) {
if(operand != typeof(string)) return typeof(object);
}
return typeof(string);
}
#endregion
#region ICustomFunctionOperatorFormattable Members
// The function's expression to be evaluated on the server.
public string Format(Type providerType, params string[] operands) {
// This example implements the function for MS Access databases only.
if(providerType == typeof(AccessConnectionProvider)) {
StringBuilder result = new StringBuilder();
result.Append("(");
for(int i = 0; i < operands.Length; i++) {
if(i > 0) result.Append(" + ");
result.AppendFormat("({0})", operands[i]);
}
result.Append(")");
return result.ToString();
}
throw new NotSupportedException(providerType.FullName);
}
}
In the code above the Register and Unregister methods simplify the global registration process of our custom function whilst the Description property of the MyConcatFunction class returns a description for our custom function, which we fetch from our resources. This example implements formatting for MS Access only.
Having done this, let’s write a Person persistent class:
Next let's write the persistent class Person.
using DevExpress.Xpo;
public class Person: XPBaseObject {
int oid;
[Key(true)]
public int Oid {
get { return oid; }
set { SetPropertyValue<int>("Oid", ref oid, value); }
}
string firstName;
public string FirstName {
get { return firstName; }
set { SetPropertyValue<string>("FirstName", ref firstName, value); }
}
string lastName;
public string LastName {
get { return lastName; }
set { SetPropertyValue<string>("LastName", ref lastName, value); }
}
//Using our custom function in PersistentAlias
[PersistentAlias("MyConcat(FirstName, ' ', LastName, ' 1')")]
public string Name {
get { return (string)EvaluateAlias("Name"); }
}
public Person(Session session) : base(session) { }
}
As you can see, in the Name property, we are using our new custom function in the same way as we would use a built-in function.
Next, we’ll add function registration into the Main method of the Program class:
[STAThread]
static void Main() {
MyConcatFunction.Register();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
Now, let’s create a new form and drop the GridControl(gridControl1), Session(session1)and XPView(xpView1) components onto it, then set the Session property of the xpView1 to session1. After that we will add properties (Name - Property accordingly) to xpView1 as shown below:
- FirstName - [FirstName]
- LastName - [LastName]
- NamePersistentAlias - [Name]
- NameViewProperty - MyConcat([FirstName], ' ', [LastName], ' 2')

Here we use the MyConcat function in the ViewProperty.
Now let’s select xpView1 as a data source of the gridControl1 component, run the designer for gridControl1 and add a column with following properties:
- Caption = Name (Unbound Expression)
- FieldName = name of the column
- ShowUnboundExpressionMenu = True
- UnboundExpression = [MyConcat]([FirstName], ' ', [LastName], ' 3')
- UnboundType = String
Finally add the following code into the form constructor:
string[][] names = new string[][] {
new string[] { "John", "Smith" },
new string[] { "Fred", "Scott" },
new string[] { "James", "King" }
};
using(UnitOfWork uow = new UnitOfWork()) {
XPCollection<Person> persons = new XPCollection<Person>(uow);
uow.Delete(persons);
uow.CommitChanges();
}
using(UnitOfWork uow = new UnitOfWork()) {
for(int i = 0; i < names.Length; i++) {
Person person = new Person(uow);
person.FirstName = names[i][0];
person.LastName = names[i][1];
}
uow.CommitChanges();
}
Then run the application to see the results:

Now that the application is running, you can select the function, from the list within the Unbound Expression Editor. To invoke this editor, click the corresponding item in the context menu for the "Name (UnboundExpression)" column.

As you can see, our new function is listed!
Well that’s all for this post, ‘til next time happy coding! 