Skip to content

fix(persistence): add AuditableEntitySaveChangesInterceptor with async-safe recursion guard#1229

Merged
iammukeshm merged 1 commit intodevelopfrom
fix/identity-dbcontext-interceptor-loop
Mar 26, 2026
Merged

fix(persistence): add AuditableEntitySaveChangesInterceptor with async-safe recursion guard#1229
iammukeshm merged 1 commit intodevelopfrom
fix/identity-dbcontext-interceptor-loop

Conversation

@iammukeshm
Copy link
Copy Markdown
Member

Summary

Supersedes #1223 — rebased onto latest develop with critical bug fixes applied.

  • AuditableEntitySaveChangesInterceptor: Automatically populates IAuditableEntity audit metadata and converts ISoftDeletable deletes into soft-delete updates
  • Async-safe recursion guard: Uses AsyncLocal<bool> (not [ThreadStatic]) to prevent StackOverflowException from nested SaveChanges calls
  • Global registration: Interceptor registered via AddHeroDatabaseOptions alongside DomainEventsInterceptor

Changes from original PR (#1223)

Fix Detail
[ThreadStatic]AsyncLocal<bool> Original guard was broken for async code — after await, execution can resume on a different thread
ExtensionsEntityEntryExtensions Renamed to internal static class to avoid namespace pollution
Added .ConfigureAwait(false) On all await calls per project conventions
Added ArgumentNullException.ThrowIfNull Guard clauses on eventData to fix CA1062 warnings
TryAddSingleton for TimeProvider Prevents duplicate registration
Merge conflict resolved PersistenceExtensions.cs merged cleanly with existing DomainEventsInterceptor registration

Test plan

  • dotnet build src/FSH.Framework.slnx — 0 errors, 0 new warnings
  • Verify audit fields are populated on entity create/update
  • Verify soft-delete converts DELETE → UPDATE with metadata
  • Verify no StackOverflow on nested SaveChanges scenarios

Credit: @cesarcastrocuba (original implementation in #1223)

🤖 Generated with Claude Code

…ursion guard

- Implemented AuditableEntitySaveChangesInterceptor to handle IAuditableEntity and ISoftDeletable.
- Added a recursion guard using [ThreadStatic] bool _isSaving to prevent StackOverflowException.
- Registered the interceptor globally in PersistenceExtensions.
- Registered TimeProvider.System as a singleton service.
- Registered DomainEventsInterceptor in PersistenceExtensions.
@iammukeshm iammukeshm merged commit 33a3282 into develop Mar 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant