You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 3 of the conjoined EF Core multi-tenancy epic (#3465). Builds on Phase 1 (registry + tenant source are useful without partitioning); integrates with Phase 2 when partitioning is enabled. CritterWatch-side gating work is tracked in the CritterWatch repo.
Goal
Runtime tenant lifecycle for conjoined EF Core tenancy, owned by Wolverine, and CritterWatch tenancy management working out of the box against a conjoined app.
Proposed design
wolverine_tenants registry table (Wolverine-owned, durability schema; a Weasel FeatureSchemaBase + IDatabaseInitializer like the partition registries):
column
notes
tenant_id (PK)
after TenantIdStyle correction
is_disabled
disabled tenants rejected at context-build and interceptor time (UnknownTenantIdException parity with Marten's master-table tenancy)
partition_suffix (null)
bucketing assignment when Phase 2 partitioning is on
created_utc / modified_utc
The registry is the authoritative tenant list for conjoined mode and exists in both plain-conjoined and partitioned-conjoined — CritterWatch tenant management works even without partitioning. Tenant lifecycle state deliberately lives here, not in Weasel's partition control tables.
ConjoinedTenantSource : IDynamicTenantSource<string>, registered in DI. That registration alone lights up CritterWatch's satellite tenant handlers (they resolve and fan out over every registered IDynamicTenantSource<string> — no satellite changes needed):
AddTenantAsync(tenantId) — registry insert; when partitioned, also AddPartitionToAllTables with suffix/bucket assignment. CritterWatch's existing auto-assign branch (empty connection string) maps to exactly this call.
AddTenantAsync(tenantId, connectionString) — invalid for conjoined ⇒ clear error ("conjoined tenants share the application database").
FindAsync / AllActiveByTenant / RefreshAsync — registry reads; the returned "connection value" is the shared app connection, masked in descriptors the way TenantedDbContextUsageSource already masks.
Descriptors: extend TenantedDbContextUsageSource<T> / DbContextUsage so a conjoined context advertises DatabaseCardinality.DynamicMultiple plus tenant ids on its single DatabaseDescriptor, giving CritterWatch what it needs to show the Tenants tab with actions.
Coexistence note: an app running Marten conjoined tenancy and EF Core conjoined tenancy registers two dynamic tenant sources; CritterWatch fans out to both by design (add-tenant hits both) — document this.
Phase 3 of the conjoined EF Core multi-tenancy epic (#3465). Builds on Phase 1 (registry + tenant source are useful without partitioning); integrates with Phase 2 when partitioning is enabled. CritterWatch-side gating work is tracked in the CritterWatch repo.
Goal
Runtime tenant lifecycle for conjoined EF Core tenancy, owned by Wolverine, and CritterWatch tenancy management working out of the box against a conjoined app.
Proposed design
wolverine_tenantsregistry table (Wolverine-owned, durability schema; a WeaselFeatureSchemaBase+IDatabaseInitializerlike the partition registries):tenant_id(PK)TenantIdStylecorrectionis_disabledUnknownTenantIdExceptionparity with Marten's master-table tenancy)partition_suffix(null)created_utc/modified_utcThe registry is the authoritative tenant list for conjoined mode and exists in both plain-conjoined and partitioned-conjoined — CritterWatch tenant management works even without partitioning. Tenant lifecycle state deliberately lives here, not in Weasel's partition control tables.
ConjoinedTenantSource : IDynamicTenantSource<string>, registered in DI. That registration alone lights up CritterWatch's satellite tenant handlers (they resolve and fan out over every registeredIDynamicTenantSource<string>— no satellite changes needed):AddTenantAsync(tenantId)— registry insert; when partitioned, alsoAddPartitionToAllTableswith suffix/bucket assignment. CritterWatch's existing auto-assign branch (empty connection string) maps to exactly this call.AddTenantAsync(tenantId, connectionString)— invalid for conjoined ⇒ clear error ("conjoined tenants share the application database").DisableTenantAsync/EnableTenantAsync/AllDisabledAsync— registry flag.RemoveTenantAsync— registry delete; when partitioned, partition drop with data removal (Postgres detach+drop today; SQL Server pending ManagedTenantPartitions (SQL Server) parity with ManagedListPartitions: drop semantics, bucketing, back-fill weasel#362 item 1).FindAsync/AllActiveByTenant/RefreshAsync— registry reads; the returned "connection value" is the shared app connection, masked in descriptors the wayTenantedDbContextUsageSourcealready masks.Admin convenience API mirroring Marten's:
host.AddWolverineManagedTenantsAsync(params string[] tenantIds)/(Dictionary<string,string> tenantToSuffix)/RemoveWolverineManagedTenantsAsync(...), returning WeaselTablePartitionStatus[].Descriptors: extend
TenantedDbContextUsageSource<T>/DbContextUsageso a conjoined context advertisesDatabaseCardinality.DynamicMultipleplus tenant ids on its singleDatabaseDescriptor, giving CritterWatch what it needs to show the Tenants tab with actions.Coexistence note: an app running Marten conjoined tenancy and EF Core conjoined tenancy registers two dynamic tenant sources; CritterWatch fans out to both by design (add-tenant hits both) — document this.