Scope / Context Manager
Definition
Section titled “Definition”The Scope Manager pattern encapsulates a context change in an IDisposable
object. The context is modified at scope creation and automatically restored
on Dispose(), guaranteeing a return to the previous state even when an
exception occurs.
Diagram
Section titled “Diagram”sequenceDiagram
participant A as Application
participant CT as CurrentTenant
participant AL as AsyncLocal
A->>CT: Change(tenantB)
CT->>AL: Save previous state (tenantA)
CT->>AL: Write tenantB
CT-->>A: IDisposable (TenantScope)
A->>A: Execute in tenantB context
A->>CT: Dispose()
CT->>AL: Restore tenantA
Implementation in Granit
Section titled “Implementation in Granit”| Scope | File | Managed context |
|---|---|---|
TenantScope | src/Granit.MultiTenancy/CurrentTenant.cs | ICurrentTenant.Change(tenantId) — restores previous tenant |
FilterScope | src/Granit.Core/DataFiltering/DataFilter.cs | IDataFilter.Disable<T>() — re-enables the filter |
| Wolverine behaviors | src/Granit.Wolverine/Behaviors/UserContextBehavior.cs | IWolverineUserContextSetter.Change() — restores user |
All scopes use AsyncLocal<T> for thread-safe propagation across
async/await boundaries.
Rationale
Section titled “Rationale”The C# using pattern guarantees Dispose() even when an exception occurs,
eliminating the risk of context leaks (a tenant remaining active after an
error).
Usage example
Section titled “Usage example”// Temporary tenant change -- automatic restorationusing (currentTenant.Change(adminTenantId)){ // All queries in this scope target adminTenantId List<AuditLog> logs = await db.AuditLogs.ToListAsync(ct);} // Previous tenant is automatically restored
// Temporarily disable the soft delete filterusing (dataFilter.Disable<ISoftDeletable>()){ // Deleted records are visible List<Patient> allPatients = await db.Patients.ToListAsync(ct);} // Filter is automatically re-enabled