Ok guys, my buds and I were bitching about the case sensitivity in the references dialog in the new BPM and Function Apps.
We don’t like it, and some of us want all the references returned with no paging.
So I solved it here:
And then @Olga gave me some light ribbing because mine is slower:
Well, I couldn’t stand it, so I made another one.
I wrote it, then had AI clean it, then I cleaned up the AI
, and eventually I ended up with this:
Edit: File lol →
ReferencesSpeedupKineticWithCache.bpm (15.3 KB)
This is a pre-processing BPM on Ice.BO.BpMethod.GetAvailableReferencesWithFilter
Reference Cache
This BPM implements a cached version of GetAvailableReferences(referenceGroup) using XXXDef as a backing store.
Cache Behavior
The cache is stored in XXXDef with the following keys:
- referenceGroup is
AssembliesorExternals
| Field | Value |
|---|---|
| ProductID | EP |
| TypeCode | Custom |
| Key1 | References |
| Key2 | referenceGroup |
| Key3 | not set |
Cache metadata is stored as:
| Field | Purpose |
|---|---|
| SysCharacter01 | Last refresh timestamp (ISO-8601 format) |
| Content | Serialized JSON payload containing the reference list |
The cache automatically refreshes when:
- No cache record exists
- The cached timestamp cannot be parsed
- The cache is older than
cacheTimeOutHours - A force refresh is requested
Special Filter Commands
The filter parameter supports special commands.
Force Refresh
(Can be set in forceRefreshString)
^
Forces the cache to refresh before returning results.
Return All Results
#
Returns all cached references without paging.
@josecgomez ![]()
Return All Matching Results
#Microsoft
Returns all references matching the filter without paging.
Equivalent to:
- Filter by
"Microsoft" - Case-Insensitive
- Disable paging
Normal Filtering
Any other filter value performs a case-insensitive contains search against ReferenceInfo.Name.
Example:
Microsoft
Returns paged results where:
reference.Name.Contains("Microsoft", StringComparison.OrdinalIgnoreCase)
Paging
Paging is applied after filtering.
.Skip((pageNum - 1) * pageSize)
.Take(pageSize)
Paging is skipped when the filter begins with #.
Fallback Behavior
This BPM executes in Pre-Processing.
If the cache path succeeds:
MarkCallCompleted();
is called and the base BO method is skipped.
If any exception occurs:
- The error is logged
- The BPM does not call
MarkCallCompleted() - Processing falls through to the base BO implementation
Code
/*
* Author: Kevin Lincecum
*
* Reference Cache
*
* Caches GetAvailableReferences(referenceGroup) results in XXXDef to reduce repeated BO calls and improve response time.
*
* Cache Storage:
* ProductID = EP
* TypeCode = Custom
* Key1 = References
* Key2 = referenceGroup
* Key3 = not set
* SysCharacter01 = Last refresh timestamp (ISO-8601)
* Content = Serialized JSON payload
*
* Cache Refresh Conditions:
* - Force refresh requested (forceRefreshString defined below)
* - Cache record does not exist
* - Cached timestamp is invalid
* - Cache age exceeds cacheTimeOutHours
*
* Filter Commands:
* ^ Force cache refresh
* # Return all records (no paging)
* #<text> Filter by text and return all matches (no paging)
* <text> Filter by text and apply paging
*
* Filtering:
* Case-insensitive contains match against ReferenceInfo.Name.
*
* Paging:
* Applied after filtering using pageNum/pageSize.
* Disabled when filter begins with (returnAllString defined below).
*
* Execution:
* Runs in Pre-Processing. On success, MarkCallCompleted() is called
* and the base BO method is skipped. On failure, the exception is logged
* and processing falls through to the base implementation.
*/
//using Newtonsoft.Json;
//ref: Newtonsoft.Json;
var cacheTimeOutHours = 24;
var forceRefreshString = "^";
var returnAllString = "#";
// Special filter values
bool forceRefresh = filter == forceRefreshString;
bool returnAll = !string.IsNullOrWhiteSpace(filter) && filter.StartsWith(returnAllString);
//Put it back lol
if (forceRefresh)
{
filter = "";
}
else if (returnAll)
{
filter = filter.Substring(1);
}
try
{
// JSON payload of the cached reference data.
var cachedDataJson = "";
// Look for an existing cache entry for this reference group.
var cachedRow = Db.XXXDef.FirstOrDefault(x =>
x.Company == CompanyID &&
x.ProductID == "EP" &&
x.TypeCode == "Custom" &&
x.Key1 == "References" &&
x.Key2 == referenceGroup);
DateTime cachedDate;
// Refresh the cache when:
// 1. Forced by the caller
// 2. No cache row exists
// 3. Cached timestamp is invalid
// 4. Cache is older than 4 hours
if (forceRefresh || cachedRow == null || !DateTime.TryParse(cachedRow.SysCharacter01, out cachedDate) || DateTime.Now - cachedDate > TimeSpan.FromHours(cacheTimeOutHours))
{
// Get fresh reference data from the BO.
CallService<Ice.Contracts.BpMethodSvcContract>(bpm =>
{
var refs = bpm.GetAvailableReferences(referenceGroup);
cachedDataJson = JsonConvert.SerializeObject( refs, Formatting.Indented);
});
// Save/update the cache.
CallService<Ice.Contracts.GenXDataSvcContract>(gx =>
{
// Load existing cache row or create a new tableset.
var ts = cachedRow == null
? new GenXDataTableset()
: gx.GetByID(CompanyID, "EP", "Custom", "", "References", referenceGroup, "");
// Create a new cache record if one doesn't exist.
if (cachedRow == null)
gx.GetNewXXXDef(ref ts, CompanyID, "EP", "Custom", "", "References", referenceGroup);
var row = ts.XXXDef.First();
// Existing rows need a before-image and RowMod for update processing.
if (cachedRow != null)
{
var bi = new XXXDefRow();
BufferCopy.Copy(row, bi);
ts.XXXDef.Add(bi);
row.RowMod = "U";
}
// Store refresh timestamp and serialized data.
row.SysCharacter01 = DateTime.Now.ToString("o");
row.Content = cachedDataJson;
gx.Update(ref ts);
});
}
else
{
// Use the cached JSON payload.
cachedDataJson = cachedRow.Content;
}
// Deserialize cached JSON into the reference list.
var cachedList = JsonConvert.DeserializeObject<List<Ice.Contracts.BO.BpMethod.ReferenceInfo>>(cachedDataJson);
// Apply case-insensitive name filtering when a filter is provided.
var filteredList = string.IsNullOrWhiteSpace(filter)
? cachedList
: cachedList
.Where(x => !string.IsNullOrEmpty(x.Name) && x.Name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0)
.ToList();
// Return the requested page.
result = (returnAll ? filteredList : filteredList
.Skip((pageNum - 1) * pageSize)
.Take(pageSize))
.ToList();
// This is pre-processing. If cache handling succeeds, skip the base method.
MarkCallCompleted();
}
catch(Exception ex)
{
// Cache failed; allow base method to run.
Ice.Diagnostics.Log.WriteEntry($"Reference cache failed; falling back to base method. {ex}");
}
It’s blazing fast now!
- It’s a little slow on a cache refresh, but the interval is configurable.
- If you’re cloud, how often do they update those?.. Ever

Older Simpler Version
Click Me Lol
Pre-Processing on Ice.BO.BpMethod.GetAvailableReferencesWithFilter
CallService<Ice.Contracts.BpMethodSvcContract>(bpm =>
{
var refs = bpm.GetAvailableReferences(referenceGroup);
result = refs.Where(x => x.Name.Contains(filter, StringComparison.OrdinalIgnoreCase)).ToList();
});
MarkCallCompleted();