Blogs

Gary's Blog

Data Layer – 11.1 Sneak Peek – Custom Functions Part 1

     

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:

  1. ICustomFunctionOperator – is responsible for base functionality such as function name, result type and client implementation of the function.
  2. ICustomFunctionOperatorFormattable – is responsible for the formatting (implementation) of the function on the server (database) side.
  3. 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')

ViewProperty Collection Editor

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:

Sample App

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.

Expression Editor

As you can see, our new function is listed!

Well that’s all for this post, ‘til next time happy coding! Smile

Published Mar 16 2011, 04:22 PM by Gary Short (DevExpress)
Filed under:
Technorati tags: v2011.1
Bookmark and Share

Comments

 

Gerhard Achrainer said:

Wow, that's great news!

Does that mean we can write our own functions in Reports (Calculated Fields) too?

March 21, 2011 9:46 AM
 

Michael Proctor [DX-Squad] said:

Gary, the team should be commended for this. Great feature, massive improvement over the Custom() syntax.

March 21, 2011 10:36 AM
 

Steven Rasmussen said:

This is Great!  Do they also show up in the FilterControl?

March 21, 2011 10:59 AM
 

Slava D (DevExpress) said:

@Gerhard Achrainer:

Custom functions can be used at any place where the CriteriaOperator is used.

So, it should also work in XtraReports.

@Steven Rasmussen:

I'm afraid that FilterControl does not support custom as well as built-in functions.

March 22, 2011 7:44 AM
 

Hedi Guizani said:

Great feature indeed but the fact that the function can only be globaly registred can cause some inconviences

We may want to create a custom function to be used only for a specific grid an don't want to see it in other grid's expression evolution

a simple solution could be the list of functions in the expression form would be public so we can hide the unneeded functions

March 23, 2011 6:05 PM
 

Robert Fuchs said:

With the expression being evaluated on the server I hope that this will work in Server Mode (XAF).

Will it?

March 23, 2011 9:31 PM
 

Slava D (DevExpress) said:

@Hedi Guizani:

Indeed, there is no necessity to register custom functions globally.

It is enough to do it in the XPDictionary and the SqlConnectionProvider (This feature is already supported in XPO 10.2. There are examples in XPO tutorials.). But in this case custom functions will not be visible in ExpressionEditor and will work only for querying objects in xpo.

@Robert Fuchs:

Yes, Server Mode supports custom functions.

March 24, 2011 3:10 AM
 

Shaw Innes said:

Could this feature (or another ?) be used to implement the use of server-side functions also?  For example, I have a SQL Server 2008 (or PostGIS) backend to my XPO objects and I want to return a geographic object in a particular formet...

I can do select ST_AsText(field) in the query, but then I have to manually map it later... could I bind this using an XPO attribute or custom function?

May 5, 2011 4:03 AM
 

che che 1 said:

Hi,

Does this exist in asp.net components ?

November 3, 2011 5:26 AM
More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.