Skip to content

Compile a rule into a delegate

This operation turns a NotificationRule's string metadata (member name, operator name, target value) into a LINQ expression tree over the model type T, then compiles it into a runnable delegate. It is the reflection-and-expression core of the engine and the location of the library's principal performance finding: nothing here is cached.

Field Value
Operation ID OP-CIA-03
Trigger A caller request (public static method), or Evaluate one rule.
Inputs r (NotificationRule).
Outputs A compiled Func<T,TResult>.
Primary components NotificationRuleEngine.CompileRule<T,TResult>, NotificationRuleEngine.BuildExpr<T>
Entry point Services/SAIF.Services.ClaimInfoApi.Core/NotificationRuleEngine.cs (lines 73–119)

Steps

  1. Create a parameter expression of type T.
  2. Build the expression body with BuildExpr<T>, choosing the first matching branch:
  3. Method branch: if typeof(T).GetMethod(r.MemberName) finds a public method, convert TargetValue to the method's first parameter type with Convert.ChangeType, and emit model.Method(value). The literal string "null" is treated as a null value (CIA-BR-008). (CIA-BR-009)
  4. Property-read branch: if OperatorName is null, emit model.Property (CIA-BR-010).
  5. Binary-operator branch: if OperatorName parses as a System.Linq.Expressions.ExpressionType name (for example Equal, GreaterThan), convert TargetValue to the property type and emit Expression.MakeBinary(op, model.Property, value) (CIA-BR-011).
  6. Member-method branch: otherwise, treat OperatorName as a public method on the property's type (for example Contains); convert TargetValue to that method's first parameter type and emit model.Property.Method(value) (CIA-BR-012).
  7. Wrap the body in Expression.Lambda<Func<T,TResult>> and call .Compile().
  8. No caching is performed: the call Expression.Lambda<Func<T, TResult>>(body, parameter).Compile() runs once per rule, per model, per call to this operation — see Architecture: Design constraints.
  9. Failure behavior: there is no validation or try/catch.
  10. An unknown member name produces a NullReferenceException, ArgumentNullException, or ArgumentException from the Expression factories*.
  11. A non-convertible TargetValue produces InvalidCastException or FormatException from Convert.ChangeType.
  12. A delegate whose type does not match TResult produces ArgumentException.
  13. All of these propagate to the caller.

Process flow

The following diagram visualizes the expression-building and compilation flow.

flowchart TD
    Start([Trigger]) --> Param["Create parameter<br>expression of type T"]
    Param --> Branch{"BuildExpr branch"}
    Branch -->|Method match| Method["model.Method(value)"]
    Branch -->|No operator| PropRead["model.Property"]
    Branch -->|Operator is ExpressionType| Binary["Expression.MakeBinary(op,<br>model.Property, value)"]
    Branch -->|Other operator| MemberMethod["model.Property.Method(value)"]
    Method --> Lambda["Expression.Lambda&lt;Func&lt;T,TResult&gt;&gt;<br>.Compile() — not cached"]
    PropRead --> Lambda
    Binary --> Lambda
    MemberMethod --> Lambda
    Lambda --> Done([Return compiled Func delegate])

Failure and edge-case behavior

  • No delegate cache exists: every call recompiles the expression tree from scratch, even for the same rule/model-type pair evaluated repeatedly — a tech-debt/performance constraint, not a mitigated design choice*.
  • No validation of MemberName, OperatorName, or TargetValue occurs before expression construction; malformed metadata surfaces as a runtime exception at compile or invoke time rather than a checked error.

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.

Sources

  • Services/SAIF.Services.ClaimInfoApi.Core/NotificationRuleEngine.cs