Apply functions are functions that you can define as expressions to apply to your data in Redis. In essence, they allow you to combine your data together, and extract the information you want.
For the remainder of this article we will be using this data model:
[Document]
public class Employee
{
[Indexed(Aggregatable = true)]
public string Name { get; set; }
[Indexed]
public GeoLoc? HomeLoc { get; set; }
[Indexed(Aggregatable = true)]
public int Age { get; set; }
[Indexed(Aggregatable = true)]
public double Sales { get; set; }
[Indexed(Aggregatable = true)]
public double SalesAdjustment { get; set; }
[Searchable(Aggregatable = true)]
public string Department { get; set; }
[Indexed(Aggregatable = true)]
public long LastOnline { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
Apply
is a method on the RedisAggregationSet<T>
class which takes two arguments, each of which is a component of the apply function.
First it takes the expression that you want Redis to execute on every record in the pipeline, this expression takes a single parameter, an AggregationResult<T>
, where T
is the generic type of your RedisAggregationSet
. This AggregationResult has two things we should think about, first it contains a RecordShell
which is a placeholder for the generic type, and secondly it has an Aggregations
property - which is a dictionary containing the results from your pipeline. Both of these can be used in apply functions.
The second component is the alias, that's the name the result of the function is stored in when the pipeline executes.
Our data model has two properties related to sales, Sales
, how much the employee has sold, and SalesAdjustment
, a figure used to adjust sales based off various factors, perhaps territory covered, experience, etc. . . The idea being that perhaps a fair way to analyze an employee's performance is a combination of these two fields rather than each individually. So let's say we wanted to find what everyone's adjusted sales were, we could do that by creating an apply function to calculate it.
var adjustedSales = employeeAggregations.Apply(x => x.RecordShell.SalesAdjustment * x.RecordShell.Sales,
"ADJUSTED_SALES");
foreach (var result in adjustedSales)
{
Console.WriteLine($"Adjusted Sales were: {result["ADJUSTED_SALES"]}");
}
Functions that use arithmetic and math can use the mathematical operators +
for addition, -
for subtraction, *
for multiplication, /
for division, and %
for modular division, also the ^
operator, which is typically used for bitiwise exclusive-or operations, has been reserved for power functions. Additionally, you can use many System.Math
library operations within Apply functions, and those will be translated to the appropriate methods for use by Redis.
You can also apply multiple string functions to your data, if for example you wanted to create a birthday message for each employee you could do so by calling String.Format on your records:
var birthdayMessages = employeeAggregations.Apply(x =>
string.Format("Congratulations {0} you are {1} years old!", x.RecordShell.Name, x.RecordShell.Age), "message");
await foreach (var message in birthdayMessages)
{
Console.WriteLine(message["message"].ToString());
}
You can also perform functions on time data in Redis. If you have a timestamp stored in a useable format, a unix timestamp or a timestamp string that can be translated from strftime, you can operate on them. For example if you wanted to translate a unix timestamp to YYYY-MM-DDTHH:MM::SSZ you can do so by just calling ApplyFunctions.FormatTimestamp on the record inside of your apply function. E.g.
var lastOnline = employeeAggregations.Apply(x => ApplyFunctions.FormatTimestamp(x.RecordShell.LastOnline),
"LAST_ONLINE_STRING");
foreach (var employee in lastOnline)
{
Console.WriteLine(employee["LAST_ONLINE_STRING"].ToString());
}
Another useful function is the GeoDistance
function, which allows you computer the distance between two points, e.g. if you wanted to see how far away from the office each employee was you could use the ApplyFunctions.GeoDistance
function inside your pipeline:
var officeLoc = new GeoLoc(-122.064181, 37.377207);
var distanceFromWork =
employeeAggregations.Apply(x => ApplyFunctions.GeoDistance(x.RecordShell.HomeLoc, officeLoc), "DistanceToWork");
await foreach (var element in distancesFromWork)
{
Console.WriteLine(element["DistanceToWork"].ToString());
}
Function | Type | Description | Example |
---|---|---|---|
Log10 | Math | yields the 10 base log for the number | Math.Log10(x["AdjustedSales"]) |
Abs | Math | yields the absolute value of the provided number |
|
Ceil | Math | yields the smallest integer not less than the provided number |
|
Floor | Math | yields the smallest integer not greater than the provided number |
|
Log | Math | yields the Log base 2 for the provided number |
|
Exp | Math | yields the natural exponent for the provided number (e^y) |
|
Sqrt | Math | yields the Square root for the provided number |
|
Function | Type | Description | Example |
---|---|---|---|
ToUpper | String | yields the provided string to upper case |
|
ToLower | String | yields the provided string to lower case |
|
StartsWith | String | Boolean expression - yields 1 if the string starts with the argument |
|
Contains | String | Boolean expression - yields 1 if the string contains the argument |
|
Substring | String | yields the substring starting at the given 0 based index, the length of the second argument, if the second argument is not provided, it will simply return the balance of the string |
|
Format | String | Formats the string based off the provided pattern |
|
Split | String | Split's the string with the provided string - unfortunately if you are only passing in a single splitter, because of how expressions work, you'll need to provide string split options so that no optional parameters exist when building the expression, just pass |
|
Function | Type | Description | Example |
---|---|---|---|
ApplyFunctions.FormatTimestamp | time | transforms a unix timestamp to a formatted time string based off strftime conventions |
|
ApplyFunctions.ParseTime | time | Parsers the provided formatted timestamp to a unix timestamp |
|
ApplyFunctions.Day | time | Rounds a unix timestamp to the beginning of the day |
|
ApplyFunctions.Hour | time | Rounds a unix timestamp to the beginning of current hour |
|
ApplyFunctions.Minute | time | Round a unix timestamp to the beginning of the current minute |
|
ApplyFunctions.Month | time | Rounds a unix timestamp to the beginning of the current month |
|
ApplyFunctions.DayOfWeek | time | Converts the unix timestamp to the day number with Sunday being 0 |
|
ApplyFunctions.DayOfMonth | time | Converts the unix timestamp to the current day of the month (1..31) |
|
ApplyFunctions.DayOfYear | time | Converts the unix timestamp to the current day of the year (1..31) |
|
ApplyFunctions.Year | time | Converts the unix timestamp to the current year |
|
ApplyFunctions.MonthOfYear | time | Converts the unix timestamp to the current year |
|