How to: Implement Custom Functions and Criteria in LINQ to XPO
- 4 minutes to read
To make custom functions or custom criteria available in LINQ to XPO expressions, all that you need to do is add a corresponding interface implementation (ICustomFunctionOperatorQueryable or ICustomCriteriaOperatorQueryable) to your function class declarations. For custom criteria, you need to also extend the registration code with the CustomCriteriaManager.RegisterCriterion or CustomCriteriaManager.RegisterCriteria method call (see details below).
Click one of the following links to quickly navigate to the desired section of the article.
Implementing Custom Functions
The code sample below demonstrates how to define the MyConcat custom function by implementing the ICustomFunctionOperatorQueryable interface. Take special note of the static Register and Unregister methods we added to simplify the function’s registration.
using System;
using System.Text;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using DevExpress.Data.Filtering;
public class MyConcatFunction : ICustomFunctionOperatorQueryable, ICustomFunctionOperator, ICustomFunctionOperatorFormattable {
const string FunctionName = nameof(MyConcat);
static readonly MyConcatFunction instance = new MyConcatFunction();
public static void Register() {
CriteriaOperator.RegisterCustomFunction(instance);
}
public static bool Unregister() {
return CriteriaOperator.UnregisterCustomFunction(instance);
}
#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 ICustomFunctionOperatorQueryable Members
public System.Reflection.MethodInfo GetMethodInfo() {
return typeof(MyConcatFunction).GetMethod(FunctionName);
}
#endregion
#region ICustomFunctionOperatorFormattable Members
public string Format(Type providerType, params string[] operands) {
if(providerType == typeof(DevExpress.Xpo.DB.MSSqlConnectionProvider)) {
return "CONCAT(" + string.Join(", ", operands) + ")";
}
throw new NotSupportedException();
}
#endregion
// The method name must match the function name (specified via FunctionName).
public static string MyConcat(string string1, string string2, string string3, string string4) {
return (string)instance.Evaluate(string1, string2, string3, string4);
}
}
To register the MyConcat custom function, call its Register method. If you do not need this custom function anymore, call its Unregister method.
MyConcatFunction.Register();
The MyConcat function is ready now to be used in LINQ to XPO expressions.
using System;
using DevExpress.Xpo;
using System.Linq;
// ...
var result = from p in new XPQuery<Person>(Session.DefaultSession)
select new { p.FirstName, p.LastName, NameLinqToXpo =
MyConcatFunction.MyConcat(p.FirstName, " ", p.LastName, " (Linq To Xpo)") };
Note that the MyConcatFunction.MyConcat function requires four parameters, rather than a parameter array, which is more convenient. A fixed parameter list is a restriction for ICustomFunctionOperatorQueryable interface implementations - they cannot handle parameter arrays passed as function parameters because LINQ returns these arrays as one argument to a custom function. To overcome this restriction, implement custom criteria with which you can handle any number of function parameters using parameter arrays, and create a corresponding CriteriaOperator object.
Implementing Custom Criteria
Unlike custom functions, custom criteria allow you to substitute custom function calls in LINQ to XPO expressions with CriteriaOperator counterparts, which are then evaluated to determine the return value. Custom criteria declarations are similar to custom function declarations, except that the ICustomFunctionOperatorQueryable interface is replaced with ICustomCriteriaOperatorQueryable, and the registration code is extended with corresponding CustomCriteriaManager method calls.
Consider the extension of the MyConcat function implemented above with the capability to accept an array as its parameter with the help of custom criteria. The only changes in the MyConcatFunction declaration are as follows.
- Updated the MyConcatFunction class’ declaration.
- Updated the Register and Unregister methods.
- Updated the MyConcat function implementation.
- Removed the ICustomFunctionOperatorQueryable interface implementation.
- Inserted the ICustomCriteriaOperatorQueryable interface implementation.
public class MyConcatFunction : ICustomCriteriaOperatorQueryable {
// ...
public static void Register() {
CriteriaOperator.RegisterCustomFunction(instance);
CustomCriteriaManager.RegisterCriterion(instance);
}
public static bool Unregister() {
bool unregisterCriterion = CustomCriteriaManager.UnregisterCriterion(instance);
return CriteriaOperator.UnregisterCustomFunction(instance) && unregisterCriterion;
}
// ...
// The method name must match the function name (specified via FunctionName)
public static string MyConcat(params string[] strings) {
StringBuilder sb = new StringBuilder();
foreach(string str in strings) {
sb.Append(str);
}
return sb.ToString();
}
#region ICustomCriteriaOperatorQueryable Members
public System.Reflection.MethodInfo GetMethodInfo() {
return typeof(MyConcatFunction).GetMethod(FunctionName);
}
public CriteriaOperator GetCriteria(params CriteriaOperator[] operands) {
if(operands == null || operands.Length != 1 || !(operands[0] is MemberInitOperator))
throw new ArgumentException(FunctionName);
CriteriaOperatorCollection operandCollection = new CriteriaOperatorCollection();
// Iterate operands[0].Members for function parameters
// and populate a criteria operator collection.
foreach(XPMemberAssignment operand in ((MemberInitOperator)operands[0]).Members) {
operandCollection.Add(operand.Property);
}
// MyConcat is returned as a CriteriaOperator object (here, FunctionOperator).
return new FunctionOperator(FunctionName, operandCollection);
}
}
The MyConcat function now accepts any number of parameters.
// ...
var result = from p in new XPQuery<Person>(Session.DefaultSession)
select new { p.FirstName, p.LastName, NameLinqToXpo =
MyConcatFunction.MyConcat(p.FirstName, " ", p.LastName, " (Linq To Xpo)", " ", "<Addon>") };