Hot Reload
ExpressiveSharp supports .NET hot reload. When you edit the body of an [Expressive] member during a dotnet watch (or IDE hot-reload) session, the next query execution uses the new expression — no manual cache clear, app restart, or rebuild required.
How It Works
Three things happen on a hot-reload event:
- Source generator re-runs incrementally. Roslyn re-emits the per-assembly
ExpressionRegistryso the factory methods produce lambdas built from the new IL. - The runtime invokes ExpressiveSharp's
[MetadataUpdateHandler]. This handler callsResetMap()on each affected assembly's generated registry (rebuilding theMethodHandle → LambdaExpressiondictionary) and clears the resolver and replacer caches. - The next query expands fresh. EF Core (and MongoDB) integrations call
ExpandExpressives()on every query execution, so the new expression body is woven in immediately.
EF Core's compiled-query cache keys off the post-expansion tree shape. A structural change to an [Expressive] body produces a different cache key, so a fresh SQL compilation happens automatically.
Caveats
EF.CompileQuery results are snapshotted
EF.CompileQuery invokes expansion once, at compile time, and returns a delegate. Hot-reloading an [Expressive] member after the delegate is built does not retroactively update it. To pick up the new body, recreate the compiled query.
Manually captured expression trees are frozen
If you call .ExpandExpressives() yourself and store the result in a field or static, the tree is captured at that moment. Hot reload does not rewrite already-materialized trees held in user memory. Prefer the per-execution path (AsExpressive() + LINQ, or the ExpressiveQueryCompiler decorator wired by UseExpressives()).
Constant-only edits behave like normal EF Core parameterization
Changing p.Status == 1 to p.Status == 2 inside an [Expressive] member reuses the same compiled SQL with a different parameter — exactly the same behaviour as inline literals. This is the EF Core constant-parameterization rule, not an ExpressiveSharp limitation.
Rude edits still need a restart
Signature changes, member removal, attribute edits, and other rude edits require an app restart — same as any other dotnet watch rude edit. Body-only changes are the supported case.
Verifying It Works
Start your app under
dotnet watch runwith EF Core SQL logging enabled (LogTo(Console.WriteLine, LogLevel.Information)).Run a query that uses an
[Expressive]member:csharppublic sealed class Product { public int Quantity { get; set; } public int Price { get; set; } [Expressive] public int Total => Quantity * Price; } // ... var rows = ctx.Products.AsExpressive().Where(p => p.Total > 100).ToList();Observe the SQL:
WHERE [p].[Quantity] * [p].[Price] > @__p_0.Edit
TotaltoQuantity * Price * 2. Save.Trigger the same query path. The new SQL should reflect the updated computation.
If the SQL does not update, check that you are not (a) calling through EF.CompileQuery, or (b) reusing a captured Expression tree built before the edit.
