Function mappings and function calls

In QuerySpec, similar to Linq, developers can define function mappings to call either DB functions or construct a SQL fragment based on input from the QuerySpec query. The Dynamic Query Engines (DQEs) of each database contain default function mappings between often used .NET methods and properties and DB constructs. Function mappings are stored in FunctionMapping instances.

Custom FunctionMappingStore instances and function mappings

To override pre-defined FunctionMapping definitions from the DQE used, the developer can define a custom FunctionMappingStore with own FunctionMapping instances, which are new or override the ones in the DQE's FunctionMappingStore.

To define a custom FunctionMappingStore, follow the same procedure as with Linq. To assign a custom FunctionMappingStore to a query, set the query's CustomFunctionMappingStore property to the instance of the custom FunctionMappingStore.

Example

In the example below the method NorthwindFunctionsQS.CalculateOrderTotal() is mapped onto a function in the database using the following code: It's important to return a FunctionMappingExpression, so the QuerySpec engine knows it has to handle the object further.

internal class NorthwindFunctionsQS
{
    public static FunctionMappingExpression CalculateOrderTotal(IEntityFieldCore orderId, bool useDiscounts)
    {
        return new FunctionMappingExpression(typeof(NorthwindFunctionsQS), "CalculateOrderTotal", 2, orderId, useDiscounts);
    }

The actual mapping is defined in the custom mapping class:

internal class NorthwindFunctionMappingsQS : FunctionMappingStore
{
    /// <summary>
    /// Initializes a new instance of the <see cref="NorthwindFunctionMappings"/> class.
    /// </summary>
    public NorthwindFunctionMappings()
        : base()
    {
        // define the mapping. 
        this.Add(new FunctionMapping(typeof(NorthwindFunctionsQS), "CalculateOrderTotal", 2, "fn_CalculateOrderTotal({0}, {1})", "Northwind", "dbo"));
    }
}

To be able to use it, set the CustomFunctionMappingStore property on the query:

var qf = new QueryFactory();
var q = qf.Order.Where(OrderFields.CustomerId == "CHOPS")
                .Select(() => new
                {
                    OrderId = OrderFields.OrderId.ToValue<int>(),
                    OrderTotal = NorthwindFunctionsQS.CalculateOrderTotal(OrderFields.OrderId, true).ToValue<decimal>()
                });
q.CustomFunctionMappingStore = new NorthwindFunctionMappingsQS();
var results = adapter.FetchQuery(q);

Defining a custom function mapping

To define a custom function mapping, a .NET method has to be written which creates and returns a FunctionMappingExpression instance with the information to find the mapping in the custom FunctionMappingStore. The .NET method can be an extension method or other method, that's not important.

Pre-defined function mappings

QuerySpec comes with predefined function mappings. All function mappings are available through function mapping classes, one for each type the functions are for. These classes define per mapping one static method. The generic functions are defined on the Functions class as a static method. All function mappings re-use the DQE function mappings already available to Linq.

Some function mappings are defined as extension method on IExpression or IEntityFieldCore as well. This is done to make using them more convenient. Not every function mapping is implemented with an extension method, as it would pollute the interface a lot otherwise. Not every function mapping defined in the DQEs is available in QuerySpec, as some are tied to Linq specific constructs.

Usage example

To use the function mappings, you can specify the function call as follows:

var qf = new QueryFactory();
var q = qf.Order
            .Where(DateTimeFunctions.Year(OrderFields.OrderDate)
                    .Equal(1996));

In this example the YEAR() function is used on the Order.OrderDate field in a predicate, where the YEAR([Orders].[OrderDate]) result is compared with the value 1996. You can use the function calls like this in predicates, expressions (which can be used in predicates, order by and projections) and projections.

Boolean functions

Boolean functions are defined on the class BooleanFunctions and the following functions are available:

BooleanFunctions method .NET / C# equivalent Sql fragment
ToString(op1) op1.ToString() CASE statement resulting in 'True' or 'False'

Convert functions

Convert functions are defined on the class ConvertFunctions and the following functions are available:

ConvertFunctions method .NET / C# equivalent Sql fragment
ToBoolean(op1) Convert.ToBoolean(op1) CONVERT/CAST
ToByte(op1) Convert.ToByte(op1) CONVERT/CAST
ToChar(op1) Convert.ToChar(op1) CONVERT/CAST
ToDateTime(op1) Convert.ToDateTime(op1) CONVERT/CAST
ToDecimal(op1) Convert.ToDecimal(op1) CONVERT/CAST
ToDouble(op1) Convert.ToDouble(op1) CONVERT/CAST
ToInt16(op1) Convert.ToInt16(op1) CONVERT/CAST
ToInt32(op1) Convert.ToInt32(op1) CONVERT/CAST
ToInt64(op1) Convert.ToInt64(op1) CONVERT/CAST
ToSingle(op1) Convert.ToSingle(op1) CONVERT/CAST
ToString(op1) Convert.ToString(op1) CONVERT/CAST
Important!

The Convert.ToDecimal doesn't apply any precision/scale value as the .NET Decimal type doesn't have a scale/precision setting as well. If you require a specific precision/scale specification with the conversion in SQL, please create a custom function mapping for Convert.ToDecimal() and map it to the proper CONVERT statement compatible to your DB of choice. See the custom function mapping procedure for Linq, which is equal to the one in QuerySpec, how to create your own function mappings.

DateTime functions

DateTime functions are defined on the class DateTimeFunctions and the following functions are available:

DateTimeFunctions method NET / C# equivalent Sql fragment
AddDays(op1, op2) op1.AddDays(op2) DATEADD()
AddHours(op1, op2) op1.AddHours(op2) DATEADD()
AddMinutes(op1, op2) op1.AddMinutes(op2) DATEADD()
AddMilliseconds(op1, op2) op1.AddMilliseconds(op2) DATEADD()
AddMonths(op1, op2) op1.AddMonths(op2) DATEADD()
AddSeconds(op1, op2) op1.AddSeconds(op2) DATEADD()
Add.Years(op1, op2) op1.AddYears(op2) DATEADD()
Date(op1) op1.Date DATEADD(DATEPART exp)
Day(op1) op1.Day DAY(op1)
DayOfWeek(op1) op1.DayOfWeek DATEPART(…)
DayOfYear(op1) op1.DayOfYear DATEPART()
Hour(op1) op1.Hour DATEPART()
Millisecond(op1) op1.Millisecond DATEPART()
Month(op1) op1.Month MONTH()
Second(op1) op1.Second DATEPART()
Year(op1) op1.Year YEAR()

Decimal functions

Decimal functions are defined on the class DecimalFunctions and the following functions are available:

DecimalFunctions method .NET / C# equivalent Sql fragment
Ceiling(op1) Decimal.Ceiling(op1) CEILING()
Floor(op1) Decimal.Floor(op1) FLOOR()
Remainder(op1, op2) Decimal.Remainder(op1, op2) expression
Round(op1) Decimal.Round(op1) ROUND(op1, 0)
Round(op1, op2) Decimal.Round(op1, op2) ROUND(op1, op2)
Truncate(op1) Decimal.Truncate(op1) ROUND(op1, 0, 1)

Math functions

Math functions are defined on the class MathFunctions and the following functions are available:

MathFunctions method .NET / C# equivalent Sql fragment
Power(op1, op2) Math.Pow(op1, op2) POWER(op1, op2)

String functions

String functions are defined on the class StringFunctions and the following functions are available:

StringFunctions method .NET / C# equivalent Sql fragment
Char(op1, op2) op1.Chars[op2] SUBSTRING(op1, op2+1, 1)
Concat(op1, op2) op1 + op2 op1 + op2
IndexOf(op1, op2) op1.IndexOf(op2) CHARINDEX expression
IndexOf(op1, op2, op3) op1.IndexOf(op2, op3) CHARINDEX expression
LastIndexOf(op1, op2) op1.LastIndexOf(op2) CASE with CHAR index expr
LastIndexOf(op1, op2, op3) op1.LastIndexOf(op2, op3) CASE with CHAR index expr
Length(op1) op1.Length LEN(op1)
PadLeft(op1, op2) op1.PadLeft(op2) CASE with SPACE expression
PadLeft(op1, op2, op3) op1.PadLeft(op2, op3) CASE with REPLICATE expr
PadRight(op1, op2) op1.PadRight(op2) CASE with SPACE expr
PadRight(op1, op2, op3) op1.PadRight(op2, op3) CASE with REPLICATE expr
Remove(op1, op2) op1.Remove(op2) LEFT(op1, op2)
Remove(op1, op2, op3) op1.Remove(op2, op3) STUFF(op1, …)
Replace(op1, op2, op3) op1.Replace(op2, op3) REPLACE(op1, op2, op3)
Substring(op1, op2) op1.Substring(op2) SUBSTRING()
Substring(op1, op2, op3) op1.Substring(op2, op3) SUBSTRING()
ToLower(op1) op1.ToLower() LOWER(op1)
ToUnicode(op1) none UNICODE(op1) or equivalents
ToUpper(op1) op1.ToUpper() UPPER(op1)
Trim(op1) op1.Trim() RTRIM(LTRIM(op1))

Generic functions

Generic functions are defined on the Functions class and the following functions are available:

Functions method .NET / C# equivalent Sql fragment
Coalesce(op1, op2) op1 ?? op2 COALESCE(op1, op2)
Compare(op1, op2) op1.Compare(op2) CASE statement returning -1, 0 or 1
IIF(op1, op2, op3) op1 ? op2 : op3 CASE WHEN op1=1 THEN op2 ELSE op3 END or equivalents
Not(op1) !op1 NOT(op1)
ShiftLeft(op1, op2) none op1 * POWER(2, op2)
ShiftRight(op1, op2) none op1 / POWER(2, op2)

Extension methods on Field/Expression calling a function mapping method

A subset of the complete mapping methods have been implemented as well as extension methods IEntityFieldCore and Expression. Only a subset has been implemented to keep the API compact. The extension methods are also strongly typed, while the *Functions methods are all accepting object typed parameters.

A developer can always define custom extension methods which call into the *Functions methods to make using them easier for a particular project (e.g. when a method is used a lot).

The following methods have been defined for IExpression and IEntityFieldCore.

DateTime functions

DateTimeFunctions method Extension method
AddDays(op1, op2) AddDays(op1)
AddHours(op1, op2) AddHours(op1)
AddMinutes(op1, op2) AddMinutes(op1)
AddMilliseconds(op1, op2) AddMilliseconds(op1)
AddMonths(op1, op2) AddMonths(op1)
AddSeconds(op1, op2) AddSeconds(op1)
Add.Years(op1, op2) AddYears(op1)
Date(op1) Date()
Day(op1) Day()
Hour(op1) Hour()
Millisecond(op1) Millisecond()
Month(op1) Month()
Second(op1) Second()
Year(op1) Year()

String functions

StringFunctions method Extension method
Char(op1, op2) Char(op1)
IndexOf(op1, op2) IndexOf(op1)
Length(op1) Length()
Replace(op1, op2, op3) Replace(op1, op2)
Substring(op1, op2) Substring(op1)
Substring(op1, op2, op3) Substring(op1, op2)
ToLower(op1) ToLower()
ToUnicode(op1) ToUnicode()
ToUpper(op1) ToUpper()
Trim(op1) Trim()