Skip to main content

Context Loaders

Overview​

Context Loaders are a powerful BankLingo Process Engine feature that enables dynamic data loading into process contexts without hardcoding entity-specific logic. They provide a declarative, JSON-based approach to fetch data during process execution.

Key Benefits​

  • 🎯 Declarative: Define data loading requirements in JSON, not code
  • 🔄 Reusable: Same loaders can be used across multiple processes
  • 🚀 Dynamic: Load any entity or execute any command at runtime
  • 📦 Type-Safe: Leverages existing entity contexts and command infrastructure
  • 🔌 Extensible: Easy to add new loaders without changing core engine

Use Cases​

  1. User Task Forms: Pre-populate form fields with current entity data
  2. Service Tasks: Load reference data for validation or processing
  3. Gateway Conditions: Fetch data needed for routing decisions
  4. Event Handlers: Load entity context when external events trigger processes

Loader Formats​

Context loaders support two primary formats:

1. Entity Loader Format​

Load a complete entity context by entity type and ID.

{
"entity": "Loan",
"id": 12345
}

When to Use:

  • Need complete entity context with all properties
  • Entity ID is known at design time or available in process variables
  • Want to leverage typed context (LoanProcessContext, DepositProcessContext, etc.)

Supported Entity Types:

  • Loan - Loan accounts
  • Deposit - Deposit accounts
  • Transaction - Financial transactions
  • Contact / Customer - Customer/contact records
  • Transfer - Fund transfers
  • BillsPayment - Bills payment transactions
  • AirtimeRecharge - Airtime recharge transactions
  • DataRecharge - Data recharge transactions
  • LoanRequest - Loan applications
  • Teller - Teller transactions
  • Reconciliation - Reconciliation jobs
  • InsurancePolicy - Insurance policies
  • InsuranceClaims - Insurance claims
  • InwardTransaction - Inward payment transactions

2. Command Loader Format​

Execute a MediatR command/query to fetch specific data.

{
"cmd": "GetLoanByIdQuery",
"context": {
"id": 12345,
"includeSchedule": true
}
}

When to Use:

  • Need specific data subsets (not entire entity)
  • Require complex queries with multiple parameters
  • Want to execute business logic during data fetch
  • Need to call external services or APIs

Examples:

// Fetch customer details
{
"cmd": "GetContactByIdQuery",
"context": {"id": 67890}
}

// Get account balance
{
"cmd": "GetDepositAccountBalanceQuery",
"context": {"accountNumber": "0123456789"}
}

// Fetch transaction history
{
"cmd": "GetTransactionHistoryQuery",
"context": {
"accountNumber": "0123456789",
"startDate": "2024-01-01",
"endDate": "2024-12-31"
}
}

Using Context Loaders​

In User Task ServerScript​

Context loaders are typically executed in User Task ServerScripts to pre-populate form data before displaying to users.

Example: Loan Approval Form​

BPMN Definition:

<userTask id="Task_ApproveLoan" name="Approve Loan">
<extensionElements>
<banklingo:serverScript>
<![CDATA[
// Load loan context
var loader = {"entity": "Loan", "id": execution.getVariable("loanId")};
var loanContext = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
execution.getVariable("currentUser"),
execution.getAllVariables()
);

// Make context available to form
execution.setVariable("loan", loanContext);
]]>
</banklingo:serverScript>
</extensionElements>
</userTask>

Form Access:

// In your form component
const loan = processVariables.loan;

console.log(loan.loanAmount); // 50000
console.log(loan.customerId); // 67890
console.log(loan.status); // 0 (PENDING)
console.log(loan.statusDescription); // "PENDING"

In Service Task​

Execute loaders within service tasks for data processing.

Example: Customer Validation Task​

<serviceTask id="Task_ValidateCustomer"
name="Validate Customer"
banklingo:class="com.banklingo.services.ValidationService"
banklingo:method="validateCustomer">
<extensionElements>
<banklingo:serverScript>
<![CDATA[
// Load customer details
var loader = {
"cmd": "GetContactByIdQuery",
"context": {"id": execution.getVariable("customerId")}
};

var customerContext = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
execution.getVariable("currentUser"),
execution.getAllVariables()
);

// Perform validation
var isValid = customerContext.kycStatus === "Approved"
&& customerContext.accountStatus === "Active";

execution.setVariable("customerValid", isValid);
]]>
</banklingo:serverScript>
</extensionElements>
</serviceTask>

With Variable Substitution​

Use variable placeholders {{variableName}} to reference process variables.

Example: Dynamic Entity Loading​

// Process has variables: entityType="Loan", entityId=12345

var loader = {
"entity": "{{entityType}}", // Resolves to "Loan"
"id": "{{entityId}}" // Resolves to 12345
};

var context = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
currentUser,
execution.getAllVariables()
);

Example: Parameterized Commands​

// Process has variables: accountNumber="0123456789", startDate="2024-01-01"

var loader = {
"cmd": "GetTransactionHistoryQuery",
"context": {
"accountNumber": "{{accountNumber}}",
"startDate": "{{startDate}}",
"endDate": "2024-12-31"
}
};

var history = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
currentUser,
execution.getAllVariables()
);

API Reference​

IContextLoaderService​

The main service interface for executing context loaders.

Methods​

ExecuteContextLoaderAsync​
Code Removed

Implementation details removed for security.

Contact support for implementation guidance.

Parameters:

  • contextExpression - JSON string defining the loader (entity or command format)
  • createdBy - Username of the user executing the loader
  • variables - Optional process variables for parameter substitution

Returns: BaseProcessContext containing the loaded entity or command result data

Throws:

  • ArgumentException - Invalid or malformed context expression
  • InvalidOperationException - Entity not found or command execution failed
  • NotSupportedException - Unsupported entity type or legacy format

Example:

Code Removed

Implementation details removed for security.

Contact support for implementation guidance.

IsValidContextExpression​
Code Removed

Implementation details removed for security.

Contact support for implementation guidance.

Validates whether a context expression is in valid JSON format.

Example:

Code Removed

Implementation details removed for security.

Contact support for implementation guidance.

GetLoaderType​
Code Removed

Implementation details removed for security.

Contact support for implementation guidance.

Determines the loader type from a context expression.

Returns: "Entity", "Command", "Legacy", or "Unknown"

Example:

Code Removed

Implementation details removed for security.

Contact support for implementation guidance.


Available Context Loaders​

Entity Loaders​

Pre-configured loaders for all supported entity types.

CodeNameDescriptionExample
ENTITY_LOANEntity - LoanLoads loan entity context by ID{"entity":"Loan","id":12345}
ENTITY_CONTACTEntity - Contact/CustomerLoads contact/customer context{"entity":"Contact","id":67890}
ENTITY_DEPOSITEntity - Deposit AccountLoads deposit account context{"entity":"Deposit","id":54321}
ENTITY_TRANSACTIONEntity - TransactionLoads transaction context{"entity":"Transaction","id":98765}
ENTITY_TRANSFEREntity - TransferLoads transfer context{"entity":"Transfer","id":11111}
ENTITY_BILLS_PAYMENTEntity - Bills PaymentLoads bills payment context{"entity":"BillsPayment","id":22222}
ENTITY_AIRTIME_RECHARGEEntity - Airtime RechargeLoads airtime recharge context{"entity":"AirtimeRecharge","id":33333}
ENTITY_DATA_RECHARGEEntity - Data RechargeLoads data recharge context{"entity":"DataRecharge","id":44444}
ENTITY_LOAN_REQUESTEntity - Loan RequestLoads loan request context{"entity":"LoanRequest","id":55555}
ENTITY_TELLEREntity - Teller TransactionLoads teller transaction context{"entity":"Teller","id":66666}
ENTITY_RECONCILIATIONEntity - ReconciliationLoads reconciliation context{"entity":"Reconciliation","id":77777}
ENTITY_INSURANCE_POLICYEntity - Insurance PolicyLoads insurance policy context{"entity":"InsurancePolicy","id":88888}
ENTITY_INSURANCE_CLAIMSEntity - Insurance ClaimsLoads insurance claims context{"entity":"InsuranceClaims","id":99999}
ENTITY_INWARD_TRANSACTIONEntity - Inward TransactionLoads inward transaction context{"entity":"InwardTransaction","id":10101}

Tracing an inward-transaction event context​

For OnInwardTransactionReceived, two searchable information-level log entries expose the context at the important boundaries:

  • OnInwardTransactionReceived Hangfire input context contains the exact entityData payload received by the Administration API Hangfire worker.
  • OnInwardTransactionReceived resolved process context contains the final context after any database-loaded fields have been merged with the supplied payload. Supplied fields take precedence, and this resolved context is what process trigger conditions and scripts receive.

Both entries contain EntityId and ContextJson. To trace a specific inward transaction, search by the event name together with the persisted inward transaction ID, for example OnInwardTransactionReceived 83528. The input payload can also be found using its sessionId.

Command Loaders​

Pre-configured loaders for common query commands.

CodeNameDescriptionExample
CMD_FETCH_LOAN_DETAILSCommand - Fetch Loan DetailsFetches loan details via query{"cmd":"GetLoanByIdQuery","context":{"id":12345}}
CMD_FETCH_CUSTOMER_DETAILSCommand - Fetch Customer DetailsFetches customer details{"cmd":"GetContactByIdQuery","context":{"id":67890}}
CMD_FETCH_ACCOUNT_BALANCECommand - Fetch Account BalanceFetches account balance{"cmd":"GetDepositAccountBalanceQuery","context":{"accountNumber":"0123456789"}}
CMD_FETCH_TRANSACTION_HISTORYCommand - Fetch Transaction HistoryFetches transaction history{"cmd":"GetTransactionHistoryQuery","context":{"accountNumber":"0123456789","startDate":"2024-01-01","endDate":"2024-12-31"}}
CMD_VALIDATE_CUSTOMERCommand - Validate CustomerValidates customer information{"cmd":"ValidateCustomerCommand","context":{"customerId":67890}}

Advanced Scenarios​

Chaining Multiple Loaders​

Load multiple related entities in sequence.

// Load loan first
var loanLoader = {"entity": "Loan", "id": execution.getVariable("loanId")};
var loan = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loanLoader),
currentUser,
execution.getAllVariables()
);

// Extract customer ID from loan
execution.setVariable("customerId", loan.customerId);

// Load customer
var customerLoader = {"entity": "Contact", "id": "{{customerId}}"};
var customer = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(customerLoader),
currentUser,
execution.getAllVariables()
);

// Both contexts now available
execution.setVariable("loan", loan);
execution.setVariable("customer", customer);

Conditional Loading​

Load different entities based on process state.

var entityType = execution.getVariable("transactionType");
var entityId = execution.getVariable("transactionId");

var loader = {
"entity": entityType === "TRANSFER" ? "Transfer" :
entityType === "BILLS" ? "BillsPayment" :
entityType === "AIRTIME" ? "AirtimeRecharge" : "Transaction",
"id": entityId
};

var context = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
currentUser,
execution.getAllVariables()
);

Error Handling​

Gracefully handle loader failures.

try {
var loader = {"entity": "Loan", "id": execution.getVariable("loanId")};
var loan = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
currentUser,
execution.getAllVariables()
);

execution.setVariable("loan", loan);
execution.setVariable("loadSuccess", true);
} catch (error) {
execution.setVariable("loadSuccess", false);
execution.setVariable("loadError", error.message);

// Log error
logger.error("Failed to load loan context", error);

// Can trigger error boundary event
throw new BpmnError("LOAD_FAILED", "Failed to load entity context");
}

Custom Command Execution​

Execute custom commands with complex parameters.

var loader = {
"cmd": "CalculateLoanEligibilityCommand",
"context": {
"customerId": execution.getVariable("customerId"),
"requestedAmount": execution.getVariable("loanAmount"),
"tenor": execution.getVariable("loanTenor"),
"productCode": execution.getVariable("productCode"),
"includeAssessment": true,
"includeRiskScore": true
}
};

var eligibility = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
currentUser,
execution.getAllVariables()
);

// Access eligibility result
if (eligibility.isEligible) {
execution.setVariable("maxLoanAmount", eligibility.maxEligibleAmount);
execution.setVariable("interestRate", eligibility.recommendedRate);
} else {
execution.setVariable("rejectionReason", eligibility.reason);
}

Implementation Details​

How Entity Loaders Work​

  1. Parse JSON: Extract entity type and ID from context expression
  2. Resolve Variables: Substitute any variable placeholders ({{variableName}})
  3. Map Entity Type: Convert entity name string to ProcessEntityType enum
  4. Query Database: Fetch entity from appropriate repository using ID
  5. Convert to Dictionary: Extract all entity properties into key-value pairs
  6. Create Context: Use ProcessContextFactory.CreateContext() to generate typed context
  7. Return Context: Return fully populated BaseProcessContext (e.g., LoanProcessContext)

Key Classes:

  • ContextLoaderService.ExecuteEntityLoaderAsync()
  • ProcessContextFactory.CreateContext()
  • Entity repositories via IUnitOfWork

How Command Loaders Work​

  1. Parse JSON: Extract command name and context parameters
  2. Resolve Variables: Substitute variable placeholders in parameters
  3. Resolve Command Type: Find command class by name using reflection
  4. Create Command Instance: Instantiate command and set properties from parameters
  5. Execute via MediatR: Send command through IMediator.Send()
  6. Convert Result: Transform command result into BaseProcessContext
  7. Return Context: Return context with command result data

Key Classes:

  • ContextLoaderService.ExecuteCommandLoaderAsync()
  • MediatR IMediator interface
  • Command/Query classes implementing IRequest<T>

Variable Substitution​

Variable placeholders support multiple formats:

// Format 1: Direct variable name
{"id": "loanId"} // Looks up "loanId" in variables dict

// Format 2: Curly brace placeholder
{"id": "{{loanId}}"} // Extracts variable name, looks up value

// Format 3: Direct value (no substitution)
{"id": 12345} // Uses value as-is

Substitution Rules:

  • Variables must exist in process variables dictionary
  • Type conversion is attempted automatically (string → long, etc.)
  • Missing variables throw ArgumentException
  • Invalid type conversions throw InvalidCastException

Best Practices​

✅ DO​

  • Use entity loaders when you need complete entity context
  • Use command loaders for targeted queries or business logic execution
  • Validate loader format before execution using IsValidContextExpression()
  • Handle errors gracefully with try-catch blocks
  • Cache loaded contexts in process variables to avoid redundant queries
  • Document custom loaders in process definition comments

❌ DON'T​

  • Don't use legacy function-style loaders (e.g., FetchLoan(id)) - use JSON format
  • Don't hardcode IDs - use variable substitution for flexibility
  • Don't load large datasets - use pagination or filtering in commands
  • Don't ignore errors - always handle loader failures appropriately
  • Don't reload same entity multiple times - cache in variables

Performance Tips​

  1. Load Once, Use Many Times: Cache loaded contexts in variables

    // ❌ Bad: Load multiple times
    var loan1 = await load({"entity":"Loan","id":123});
    var loan2 = await load({"entity":"Loan","id":123}); // Redundant!

    // ✅ Good: Load once, reuse
    var loan = await load({"entity":"Loan","id":123});
    execution.setVariable("loan", loan); // Cache in variable
    // Use "loan" variable throughout process
  2. Use Specific Commands for Partial Data: Don't load entire entities for single fields

    // ❌ Bad: Load entire loan just for amount
    var loan = await load({"entity":"Loan","id":123});
    var amount = loan.loanAmount;

    // ✅ Good: Use specific command
    var result = await load({
    "cmd":"GetLoanAmountQuery",
    "context":{"id":123}
    });
  3. Batch Loading: Use commands that support batch fetching

    // ✅ Good: Load multiple loans in one query
    var loans = await load({
    "cmd":"GetLoansByIdsQuery",
    "context":{"ids":[123,456,789]}
    });

Troubleshooting​

Error: "Entity type is required in entity loader format"​

Cause: Missing or empty entity field in loader JSON.

Solution:

// ❌ Wrong
{"id": 12345}

// ✅ Correct
{"entity": "Loan", "id": 12345}

Error: "Invalid entity type: XYZ"​

Cause: Entity type doesn't match any ProcessEntityType enum value.

Solution: Use one of the supported entity types (case-insensitive):

  • Loan, Deposit, Transaction, Contact, Transfer, BillsPayment, AirtimeRecharge, DataRecharge, LoanRequest, Teller, Reconciliation, InsurancePolicy, InsuranceClaims, InwardTransaction

Error: "Entity not found: Loan with ID 12345"​

Cause: No entity exists in database with specified ID.

Solution:

  • Verify entity ID is correct
  • Check if entity was deleted
  • Ensure ID variable substitution is working correctly

Error: "Command not found: GetLoanByIdQueryXYZ"​

Cause: Command class doesn't exist or isn't registered with MediatR.

Solution:

  • Verify command name spelling
  • Ensure command implements IRequest<T>
  • Check MediatR registration in dependency injection

Error: "Variable not found: loanId"​

Cause: Referenced variable doesn't exist in process variables.

Solution:

  • Verify variable name spelling
  • Ensure variable was set before loader execution
  • Check variable scope (process instance vs. local task)

Error: "Legacy function-style loaders are no longer supported"​

Cause: Using old format like FetchLoan(12345).

Solution: Convert to JSON format:

// ❌ Legacy (deprecated)
"FetchLoan(12345)"

// ✅ New format
{"entity":"Loan","id":12345}

Migration from Legacy Loaders​

If you have existing processes using legacy function-style loaders, follow this migration guide:

Step 1: Identify Legacy Loaders​

Search for context expressions containing parentheses:

  • FetchLoan(loanId)
  • ValidateCustomer(customerId)
  • FetchAccountBalance(accountId)

Step 2: Convert to JSON Format​

Legacy FormatNew JSON Format
FetchLoan(loanId){"entity":"Loan","id":"{{loanId}}"}
ValidateCustomer(customerId){"cmd":"ValidateCustomerCommand","context":{"customerId":"{{customerId}}"}}
FetchAccountBalance(accountId){"cmd":"GetDepositAccountBalanceQuery","context":{"accountId":"{{accountId}}"}}

Step 3: Update Process Definitions​

Replace old loader calls with new JSON format in BPMN XML.

Step 4: Test Thoroughly​

  • Verify variable substitution works correctly
  • Ensure entity/command resolution succeeds
  • Check error handling paths


Summary​

Context Loaders provide a powerful, declarative way to load data into BankLingo processes:

  • Two formats: Entity-based and Command-based
  • 14 entity types supported out-of-the-box
  • Unlimited custom commands via MediatR integration
  • Variable substitution for dynamic parameter passing
  • Type-safe using existing ProcessContext infrastructure
  • Extensible - easy to add new loaders and commands

Use entity loaders for complete entity contexts, command loaders for targeted queries, and variable substitution for maximum flexibility.