{"record":{"id":"14ad4d4f8d04a359","repo":"fullstackhero/dotnet-starter-kit","slug":"at-least-one-active-tenant-is-required","errorCode":null,"errorMessage":"At least one active tenant is required.","messagePattern":"At least one active tenant is required\\.","errorType":"exception","errorClass":"CustomException","httpStatus":400,"severity":"error","filePath":"src/Modules/Multitenancy/Modules.Multitenancy/Services/TenantService.cs","lineNumber":132,"sourceCode":"\n        foreach (var initializer in scope.ServiceProvider.GetServices<IDbInitializer>())\n        {\n            await initializer.SeedAsync(cancellationToken).ConfigureAwait(false);\n        }\n    }\n\n    public async Task<string> DeactivateAsync(string id, CancellationToken cancellationToken = default)\n    {\n        var tenant = await GetTenantInfoAsync(id, cancellationToken).ConfigureAwait(false);\n        if (!tenant.IsActive)\n        {\n            throw new CustomException($\"tenant {id} is already deactivated\");\n        }\n\n        int tenantCount = (await _tenantStore.GetAllAsync().ConfigureAwait(false)).Count(t => t.IsActive);\n        if (tenantCount <= 1)\n        {\n            throw new CustomException(\"At least one active tenant is required.\");\n        }\n\n        if (tenant.Id.Equals(MultitenancyConstants.Root.Id, StringComparison.OrdinalIgnoreCase))\n        {\n            throw new CustomException(\"The root tenant cannot be deactivated.\");\n        }\n\n        tenant.Deactivate();\n        await _tenantStore.UpdateAsync(tenant).ConfigureAwait(false);\n        await RefreshTenantCacheAsync(tenant).ConfigureAwait(false);\n        return $\"tenant {id} is now deactivated\";\n    }\n\n    public async Task<bool> ExistsWithIdAsync(string id, CancellationToken cancellationToken = default) =>\n        await _tenantStore.GetAsync(id).ConfigureAwait(false) is not null;\n\n    public async Task<bool> ExistsWithNameAsync(string name, CancellationToken cancellationToken = default) =>\n        (await _tenantStore.GetAllAsync().ConfigureAwait(false)).Any(t => t.Name == name);","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Multitenancy/Modules.Multitenancy/Services/TenantService.cs#L114-L150","documentation":"DeactivateAsync counts active tenants across the store and refuses when the deactivation would leave zero active tenants (tenantCount <= 1). At least one active tenant must always exist so the system keeps a usable default/root tenancy. Note the count is computed from GetAllAsync, so all tenants are considered, not just the one being deactivated.","triggerScenarios":"Deactivating the last remaining active tenant; deactivating the root tenant when it is the only active one (count check fires before the root check); environments where all other tenants were already deactivated or deleted.","commonSituations":"Clean-up scripts deactivating tenants one by one until only one remains; test/seed environments with a single tenant; tenant offboarding that didn't account for the always-one-active invariant.","solutions":["Activate another tenant first so at least two are active, then deactivate the target.","Re-create/activate the root tenant if it was inadvertently deactivated elsewhere.","In automation, count active tenants before deactivating and stop at one.","If this is a seeded dev environment, reseed tenants so more than one is active."],"exampleFix":"// before\nawait tenantService.DeactivateAsync(lastActiveTenantId, ct);\n\n// after\nvar all = await tenantService.GetAllAsync(ct);\nvar activeCount = all.Count(t => t.IsActive);\nif (activeCount <= 1)\n    throw new InvalidOperationException(\"Activate another tenant before deactivating the last active one.\");\nawait tenantService.DeactivateAsync(lastActiveTenantId, ct);","handlingStrategy":"validation","validationCode":"var all = await tenantService.GetAllAsync(ct);\nif (all.Count(t => t.IsActive) <= 1)\n    throw new InvalidOperationException(\"Refusing: this would leave zero active tenants. Activate another first.\");","typeGuard":"bool CanDeactivate(IEnumerable<AppTenantInfo> all, string id) =>\n    all.Count(t => t.IsActive && !t.Id.Equals(id, StringComparison.OrdinalIgnoreCase)) >= 1;","tryCatchPattern":"try\n{\n    await tenantService.DeactivateAsync(id, ct);\n}\ncatch (CustomException ex) when (ex.Message == \"At least one active tenant is required.\")\n{\n    logger.LogWarning(\"Deactivation of {TenantId} blocked: last active tenant\", id);\n}","preventionTips":["Count active tenants before any deactivation batch and stop at one.","Never script deactivation of all tenants; whitelist non-essential ones.","Ensure seed/environments always contain more than one active tenant for testing offboarding.","Check the active-count invariant after bulk operations to catch drift."],"tags":["multitenancy","tenant","invariant","deactivation"],"backgroundTag":"invalid-state-transition","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}