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 {
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
// 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);
}
}
Imports System
Imports System.Text
Imports DevExpress.Xpo
Imports DevExpress.Xpo.DB
Imports DevExpress.Data.Filtering
Public Class MyConcatFunction
Implements ICustomFunctionOperatorQueryable, ICustomFunctionOperator
Private Const FunctionName As String = NameOf(MyConcat)
Private Shared ReadOnly instance As New MyConcatFunction()
Public Shared Sub Register()
CriteriaOperator.RegisterCustomFunction(instance)
End Sub
Public Shared Function Unregister() As Boolean
Return CriteriaOperator.UnregisterCustomFunction(instance)
End Function
#Region "ICustomFunctionOperator Members"
' Evaluates the function on the client.
Public Function Evaluate(ParamArray ByVal operands() As Object) As Object
Dim result As New StringBuilder()
For Each operand As String In operands
result.Append(operand)
Next operand
Return result.ToString()
End Function
Public ReadOnly Property Name() As String
Get
Return FunctionName
End Get
End Property
Public Function ResultType(ParamArray ByVal operands() As Type) As Type
For Each operand As Type In operands
If operand IsNot GetType(String) Then
Return GetType(Object)
End If
Next operand
Return GetType(String)
End Function
#End Region
#Region "ICustomFunctionOperatorQueryable Members"
Public Function GetMethodInfo() As System.Reflection.MethodInfo
Return GetType(MyConcatFunction).GetMethod(FunctionName)
End Function
#End Region
' The method name must match the function name (specified via FunctionName)
Public Shared Function MyConcat(ByVal string1 As String, ByVal string2 As String, _
ByVal string3 As String, ByVal string4 As String) As String
Return CStr(instance.Evaluate(string1, string2, string3, string4))
End Function
End Class
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();
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)") };
' This code snippet uses implicit typing.
' You will need to set 'Option Infer On' in the VB file or set 'Option Infer' at the project level:
Imports System
Imports DevExpress.Xpo
Imports System.Linq
' ...
Dim result = _
From p In New XPQuery(Of Person)(Session.DefaultSession) _
Select New With {Key p.FirstName, Key p.LastName, Key .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.
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);
}
}
Public Class MyConcatFunction
Implements ICustomCriteriaOperatorQueryable
' ...
Public Shared Sub Register()
CriteriaOperator.RegisterCustomFunction(instance)
CustomCriteriaManager.RegisterCriterion(instance)
End Sub
Public Shared Function Unregister() As Boolean
Dim unregisterCriterion As Boolean = CustomCriteriaManager.UnregisterCriterion(instance)
Return CriteriaOperator.UnregisterCustomFunction(instance) AndAlso unregisterCriterion
End Function
' ...
' The method name must match the function name (specified via FunctionName)
Public Shared Function MyConcat(ParamArray ByVal strings() As String) As String
Dim sb As New StringBuilder()
For Each str As String In strings
sb.Append(str)
Next str
Return sb.ToString()
End Function
#Region "ICustomCriteriaOperatorQueryable Members"
Public Function GetMethodInfo() As System.Reflection.MethodInfo
Return GetType(MyConcatFunction).GetMethod(FunctionName)
End Function
Public Function GetCriteria(ParamArray ByVal operands() As CriteriaOperator) As CriteriaOperator
If operands Is Nothing OrElse operands.Length <> 1 OrElse _
Not(TypeOf operands(0) Is MemberInitOperator) Then
Throw New ArgumentException(FunctionName)
End If
Dim operandCollection As New CriteriaOperatorCollection()
' Iterate operands[0].Members for function parameters
' and populate a criteria operator collection.
For Each operand As XPMemberAssignment In (CType(operands(0), MemberInitOperator)).Members
operandCollection.Add(operand.Property)
Next operand
' MyConcat is returned as a CriteriaOperator object (here, FunctionOperator).
Return New FunctionOperator(FunctionName, operandCollection)
End Function
End Class
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>") };
' This code snippet uses implicit typing.
' You will need to set 'Option Infer On' in the VB file or set 'Option Infer' at the project level:
' ...
Dim result = _
From p In New XPQuery(Of Person)(Session.DefaultSession) _
Select New With {Key p.FirstName, Key p.LastName, Key .NameLinqToXpo = _
MyConcatFunction.MyConcat(p.FirstName, " ", p.LastName, " (Linq To Xpo)", " ", "<Addon>")}
See Also