Property Calculator Troubleshooting and Performance
Overview
This page covers common Property Calculator issues and their solutions, performance optimization tips, and guidance for migrating from the legacy Property Calculator.
Expression Errors
Syntax Errors
| Error | Cause | Fix |
|---|---|---|
Unexpected token | Malformed expression syntax | Check for unmatched parentheses, missing commas, or typos in function names |
Missing closing quote | Unmatched single quote in string literal | Ensure every ' has a matching pair. Use escape() for strings containing apostrophes. |
Unknown function | Typo in function name | Check spelling — function names are case-insensitive but must be exact |
Parameter count mismatch | Wrong number of arguments to a function | Check the function reference in NCalc Expressions |
Uppercase Logical Operators
Problem: Using AND, OR, NOT in expressions causes parsing errors.
Fix: NCalc 6.x requires lowercase logical operators:
❌ %PROPERTY_{PD.A}% > 0 AND %PROPERTY_{PD.B}% > 0
✅ %PROPERTY_{PD.A}% > 0 and %PROPERTY_{PD.B}% > 0
Use the Upgrade from Legacy PC button on the dashboard to automatically convert all operators to lowercase.
Placeholder Errors
| Error | Cause | Fix |
|---|---|---|
| Property value is always null | Wrong alias, GUID, or ID in placeholder | Verify the property definition exists in the vault and the alias matches exactly |
| Expression returns the placeholder text literally | Evaluate as Expression is OFF | Turn ON the toggle for NCalc evaluation |
% operator causes parse error | Using % for modulo | Use Mod() function instead: Mod(10, 3) |
Type Mismatch Errors
| Error | Cause | Fix |
|---|---|---|
Cannot apply operator to types | Comparing/calculating with incompatible types (e.g., string + number) | Use toNumber() or tostring() to convert types explicitly |
| Date arithmetic fails | Property returns text instead of DateTime | Use parseDate() to convert: parseDate(%PROPERTY_{PD.DateText}%, 'yyyy-MM-dd') |
| Lookup comparison fails | Comparing lookup object to string | Use lookupName() to extract the display name first |
Null-Related Errors
| Error | Cause | Fix |
|---|---|---|
| Expression returns null unexpectedly | A property used in the expression is empty | Wrap with ifNull() or coalesce(): ifNull(%PROPERTY_{PD.Amount}%, 0) |
Object reference not set | Chained placeholder resolves through an empty lookup | Add a condition: Changed Propertyvalues or Basic Condition checking the lookup is not empty |
Loop Detection
What Causes Loops
Loops occur when related object updates create circular dependencies:
Object A changes → triggers update on Object B
Object B changes → triggers update on Object A
Object A changes → triggers update on Object B
... (infinite)
How Loop Detection Works
Property Calculator monitors three indicators:
- State chain detection: If the same workflow state is reached again in rapid succession within the configured time window
- Version velocity: If an object accumulates more than
MaxVersionsPerDayserver-created versions in 24 hours (default: 20). Only versions created by the M-Files server (user ID < 0) count, not real users. - Periodic full check: Every N versions (default: 100), a full history check is performed
Loop detection reads the object’s version history directly at runtime — it is not stored in the Named Value Store. There is no persistent “looping” flag to clear.
When Loop Detection Triggers
- The offending update or state transition is skipped
- A warning/error is logged (and surfaced via the Status Report if enabled)
- The object may appear as “stuck” on the dashboard if its updates keep being retried
Resolution
- Identify the loop: Check the object’s version history — you’ll see rapid server-created versions
- Break the cycle in configuration: Adjust related object update conditions:
- Add a “Changed Propertyvalues” condition so updates only fire when relevant values actually change
- Remove unnecessary cascading chains
- Use one-directional updates instead of bidirectional
Because loop detection is based on live version history (not a stored flag), the object recovers on its own once the runaway versioning stops — there is nothing to “unflag”.
Tuning Loop Detection
| Setting | Default | When to Increase | When to Decrease |
|---|---|---|---|
| State chain time window | 30s | Legitimate workflows with rapid state changes | Want faster loop detection |
| Full loop check every N versions | 100 | Performance-sensitive vaults | Want more thorough detection |
| Max server versions per 24h | 20 | Bulk import scenarios, migration operations | Want stricter loop prevention |
Stuck Objects
What “Stuck” Means
An object counts as stuck when its background update keeps failing — either an error or a target object that stays checked out. There is no dedicated “stuck queue”; failing work lives in one of three places in the processing pipeline (see Background Operations):
- A hot-queue update that has already been retried at least once.
- An entry in the cold overflow buffer’s retry chain.
- An entry in the dead-letter archive, once retries are exhausted.
For the exact internal storage locations behind each of these — useful when M-Files support asks you to check a specific value in the NVS Browser — see NVS Namespaces.
Stuck / Failing Object Lifecycle
runs, succeeds
┌───────────────────────────────────► Done ✅
│ ▲
│ runs, fails (escalating back-off) │ due, runs, succeeds
│ too much waiting work │
▼ (moved to overflow, still retrying) │
Waiting ─────────────────────────────► Retrying ─┐
▲ │ due, runs, fails
└──────┘ (back-off)
│
│ retry window exhausted
│ (14 days from first failure)
▼
Dead-Letter — needs manual action
│
│ admin fixes cause, re-queues
│ (NVS Browser)
▼
back to Waiting
Background Processing — Three Recurring Processors
Property Calculator runs three independent recurring processors (Automatic Update, State Transition, and Queue Cleanup), each on its own schedule (not one monolithic cycle). See Background Operations for what each one does and how often it runs by default.
The hot queue executes individual related-object updates in parallel with these processors. The same background rule engine is used everywhere, so all calculation modes behave identically. The side-effect modes (Send Email, Create Object, File Operation) are not run by the background engine directly, but they still execute via the object’s re-fired check-in handler when the background update re-saves the object (see Background Operations).
StuckThresholdHours — What It Really Controls
The StuckThresholdHours setting (default: 24) does not control when an object becomes stuck. It controls:
- Dashboard display: retryable stuck items are only shown once they are older than the threshold (dead-lettered objects are always shown).
- Status Reporting: only objects older than the threshold are included in the status report.
Common Causes
| Cause | Resolution |
|---|---|
| Expression error on specific object | Fix the expression or add conditions to skip the problematic case |
| Missing property on object | Ensure the property exists or use isNull()/coalesce() for safety |
| Circular related object updates | See Loop Detection |
| Object locked by another process | Wait for the lock to release, or check for conflicting automations |
| Vault extension timeout | The calculation is too complex — simplify or split into multiple steps |
How to Find Stuck Objects
- Check the dashboard Queue Status — the stuck count is displayed
- Enable Status Reporting in General Settings — writes stuck object IDs to a vault object’s Comment property
- Check application logs for error messages related to specific objects
Status Reporting
Enable Status Reporting in General Settings to have Property Calculator write a human-readable summary of stuck and dead-lettered objects to a designated vault object’s Comment property, so it is visible from an M-Files view or can drive a notification. See Configuration for how to configure the target object and optional boolean flags.
- Routine updates: Throttled to once per 24 hours to prevent version bloat
- Error detection: Updates immediately when new errors are detected
Resolution Steps
- Identify the stuck object (from dashboard, status report, or logs)
- Check the object’s properties and version history
- Test the relevant expression in the Expression Builder with the object’s values
- Fix the configuration issue
- The object will be retried automatically on its next scheduled attempt (hot-queue back-off, or the overflow retry chain drained by the Automatic Update processor)
Background Operations Not Running
Checklist
| Check | What to Look For |
|---|---|
| Dashboard status | Is the rule showing as “Stopped” or “Paused”? Start/Resume it. |
| Rule activation | Is the rule configured but never started? Use the dashboard ▶ Start button. |
| Schedule | Is the CRON expression correct? Use a CRON validator tool. |
| Search conditions | Do the conditions match any objects? Use Update Preview to test. |
| Filtering conditions | Are the additional filters too restrictive? Try removing them temporarily. |
| Vault application status | Is the Property Calculator application running? Check M-Files Admin → Applications. |
Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
Using AND/OR/NOT (uppercase) | Expression parsing error | Use lowercase: and, or, not |
Using % for modulo | Parsed as placeholder delimiter | Use Mod() function |
Missing Evaluate as Expression toggle | Expression saved as literal text | Enable the toggle |
| Wrong placeholder syntax | Value is null or literal text | Verify: %PROPERTY_{PD.Alias}% with correct alias |
| Comparing lookup to string directly | Comparison always false | Use lookupName(): lookupName(%PROPERTY_{PD.Status}%) == 'Active' |
| Missing null check | Expression fails on empty properties | Wrap with ifNull() or add condition checking property is not empty |
| Using double quotes for strings | Parse error | NCalc uses single quotes only: 'text' not "text" |
| Forgetting comma between function arguments | Parse error or wrong result | iif(condition, trueVal, falseVal) — all commas required |
| Circular related object updates | Loop detection, stuck objects | Add “Changed Propertyvalues” conditions to break cycles |
| Too many immediate updates | Slow check-in performance | Use background queue for large update sets |
| Event handler timing conflict | Values overwritten by other handlers | Switch to BeforeCheckInChangesFinalize (default) |
Performance Tips
Event Handler Timing
See Configuration for the BeforeCheckInChangesFinalize vs. BeforeCheckInChanges tradeoff — use the default (BeforeCheckInChangesFinalize) for the vast majority of calculations.
Background Operation Scheduling
| Pattern | Best For |
|---|---|
Cron: off-hours (e.g., 0 2 * * *) | Large update sets that can run overnight |
| Every X Hours | Regular maintenance tasks (expiry checks, status updates) |
| Every Cycle | One-time migrations or bulk fixes (disable after completion) |
Use Update Preview before starting large background operations to understand the scope.
Related Object Update Chains
- Keep chains short: A → B is fine. A → B → C → D → E causes performance issues.
- Use conditions: Add “Changed Propertyvalues” conditions to prevent unnecessary cascading.
- Prefer background over immediate: For chains with many targets, background processing is more efficient.
- Throttling: See Background Operations for the setting that prevents rapid-fire updates on the same object.
Expression Optimization
- Use variables for repeated calculations: If the same sub-expression appears multiple times, calculate it once with
store()and reuse withget(). - Avoid unnecessary chaining: Each chained placeholder (
A.B) requires vault API calls. Minimize chain depth. - Keep conditions specific: Narrow conditions reduce how often expressions are evaluated.
- Place cheap conditions first: In condition lists, put simple checks (property value comparison) before expensive ones (regex, advanced conditions).
Migration from the Legacy Property Calculator
Full breaking-changes reference: see Breaking Changes & Upgrade Guide for the complete list of behavioral differences vs. the legacy Property Calculator, including which items the Upgrade button fixes automatically and which need a manual edit.
Using the Migration Tool
- Open the Property Calculator dashboard in M-Files Admin
- Click the 🔄 Upgrade from Legacy PC button
- The tool automatically converts:
AND/OR/NOT→and/or/notLIKEwildcard syntax:*→%,?→_
- Review the configuration after conversion
Key Differences
| Feature | Legacy PC | Current PC |
|---|---|---|
| Expression Engine | DataTable.Compute (legacy) | NCalc 6.x (modern, secure) |
| Logical Operators | Case-insensitive (AND, and) | Lowercase only (and, or, not) |
| LIKE Wildcards | * and ? | % and _ (SQL-style) |
| Custom Functions | Limited | 50+ functions (lookup, date, regex, aggregation, file, etc.) |
| Security | DataTable injection risks | Parameterized evaluation (injection-safe) |
| Background Operations | Basic | Full dashboard management with scheduling |
| Expression Builder | Not available | Interactive testing tool |
| Condition Types | Basic only | 8 types including regex, file changes, property comparison |
Common Migration Issues
| Issue | Resolution |
|---|---|
Expressions with && or || | Replace with and / or |
Expressions with ! (NOT) | Replace with not |
String concatenation with + | Works in NCalc, but concat() is preferred for clarity |
LIKE patterns with * | Replace with %: LIKE(val, '%pattern%') |
IIF (uppercase) | Still works — function names are case-insensitive |
| DataTable-specific syntax | Rewrite using NCalc syntax (see NCalc Expressions) |