Debugging Events

//TODO: This could be simplified.

When debugging business logic, it can be difficult to understand why a property changed, what triggered the change, and which events ran in response. The DebuggerPropertyNames feature lets you set a breakpoint on any property change at runtime — without modifying code or rebuilding.

This feature is only available in Debug builds. It is completely removed in Release builds with zero performance impact.

Quick Start

  1. Run your app or test in Debug mode
  2. Hit any breakpoint (or use Debug.Break())
  3. In the Immediate Window, add the property you want to track:
SalesOrder.DebugPropertyNames.Add("Subtotal")
  1. Continue execution. The debugger will break the next time Subtotal is set or dirtied on any SalesOrder instance.

How It Works

Every entity class generated with [ApiEntity] includes a static HashSet<string> in Debug builds:

public static HashSet<string> DebugPropertyNames = [];

When a property is set — either directly or by being dirtied through a dependency — the generated code checks whether the property name is in this set. If it is, the debugger breaks and exposes diagnostic variables.

What Triggers a Break

The debugger breaks in two scenarios:

Scenario isDirty Meaning
Direct set false Code explicitly assigned a value to this property
Dependency dirtied true Another property changed and this property was marked dirty as a dependent

This distinction tells you whether you're looking at the cause or the effect of a change.

Diagnostic Variables

When the debugger breaks, these local variables are available in the Watch or Locals window:

Variable Type Description
getValue Func<TValue> Call getValue() in the Immediate window to see the value being set. Intentionally lazy to avoid side effects.
changedBy string? The event or logic method that set this property
dirtiedBy string? The property that caused this property to be dirtied (when isDirty is true)
dirtyFlag bool false = direct set, true = dirtied by dependency
eventsTree string Full tree of all events that have fired for this entity in the current context
eventPath string The chain of events leading to this specific change
context EventContext The full event context for advanced inspection

Reading eventsTree

The events tree shows a hierarchy of all events that fired, indented by depth:

Compute:SalesOrderDetail.TotalPrice
  Compute:SalesOrder.Subtotal
    Compute:SalesOrder.Total

This tells you: setting TotalPrice triggered a recompute of Subtotal, which triggered a recompute of Total.

Reading eventPath

The event path is a flat chain showing how the current breakpoint was reached:

Compute:SalesOrderDetail.TotalPrice -> Compute:SalesOrder.Subtotal

Common Scenarios

"Why did this value change?"

Track the property and inspect changedBy when it breaks:

SalesOrderDetail.DebugPropertyNames.Add("UnitPrice")

When it breaks, check changedBy — it will show the compute event or logic method that set the value (e.g., "PricingBL.ComputeUnitPrice").

"What triggered this recomputation?"

Track a computed property and check dirtiedBy:

SalesOrder.DebugPropertyNames.Add("Total")

When it breaks with dirtyFlag = true, dirtiedBy tells you which property change caused Total to be marked dirty (e.g., "Subtotal" or "ShippingAmount").

"What's the full chain of events?"

Inspect eventsTree to see every event that has fired on this entity during the current operation. This is especially useful for understanding cascading compute chains.

Track Multiple Properties

Add as many properties as you need:

SalesOrder.DebugPropertyNames.Add("Subtotal")
SalesOrder.DebugPropertyNames.Add("Total")
SalesOrder.DebugPropertyNames.Add("ShippingAmount")

Stop Tracking

Remove a specific property or clear all:

// Stop tracking one property
SalesOrder.DebugPropertyNames.Remove("Total")

// Stop tracking all properties on this entity
SalesOrder.DebugPropertyNames.Clear()

Track on Different Entities

Each entity class has its own DebugPropertyNames — they are independent:

SalesOrder.DebugPropertyNames.Add("Total")
SalesOrderDetail.DebugPropertyNames.Add("TotalPrice")
Customer.DebugPropertyNames.Add("FullName")

Important Notes

  • Debug builds only — the feature is compiled out in Release. Use dotnet build -c Debug or run from Visual Studio in Debug configuration.
  • Static collectionDebugPropertyNames is shared across all instances of an entity class. Adding "Total" breaks on every SalesOrder instance, not just one.
  • Lazy valuegetValue is a Func<TValue>. Call getValue() in the Immediate window only when you need the value. This avoids triggering additional side effects during debugging.
  • Property names are case-sensitive — use the exact C# property name (e.g., "Subtotal", not "subtotal").
  • No rebuild needed — add or remove property names at runtime through the Immediate window. No code changes or restart required.