< Summary

Information
Class: Elsa.Common.Multitenancy.DefaultTenantService
Assembly: Elsa.Common
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantService.cs
Line coverage
70%
Covered lines: 54
Uncovered lines: 23
Coverable lines: 77
Total lines: 155
Line coverage: 70.1%
Branch coverage
79%
Covered branches: 19
Total branches: 24
Branch coverage: 79.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
DisposeAsync()100%11100%
FindAsync()0%620%
FindAsync()100%210%
GetAsync()100%210%
GetAsync()100%210%
ListAsync()100%210%
ListAsync()100%210%
ActivateTenantsAsync()100%11100%
DeactivateTenantsAsync()100%22100%
RefreshAsync()75%9876.47%
GetTenantsDictionaryAsync()100%88100%
RegisterTenantAsync()100%11100%
UnregisterTenantAsync()75%4487.5%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantService.cs

#LineLine coverage
 1using Elsa.Extensions;
 2using Microsoft.Extensions.DependencyInjection;
 3
 4namespace Elsa.Common.Multitenancy;
 5
 76public class DefaultTenantService(IServiceScopeFactory scopeFactory, ITenantScopeFactory tenantScopeFactory, TenantEvent
 7{
 78    private readonly AsyncServiceScope _serviceScope = scopeFactory.CreateAsyncScope();
 79    private readonly SemaphoreSlim _initializationLock = new(1, 1);
 710    private readonly SemaphoreSlim _refreshLock = new(1, 1);
 11    private IDictionary<string, Tenant>? _tenantsDictionary;
 12    private IDictionary<Tenant, TenantScope>? _tenantScopesDictionary;
 13
 14    public async ValueTask DisposeAsync()
 15    {
 316        await _serviceScope.DisposeAsync();
 317        _initializationLock.Dispose();
 318    }
 19
 20    public async Task<Tenant?> FindAsync(string id, CancellationToken cancellationToken = default)
 21    {
 022        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 023        return dictionary.TryGetValue(id.EmptyIfNull(), out var tenant) ? tenant : null;
 024    }
 25
 26    public async Task<Tenant?> FindAsync(TenantFilter filter, CancellationToken cancellationToken = default)
 27    {
 028        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 029        return filter.Apply(dictionary.Values.AsQueryable()).FirstOrDefault();
 030    }
 31
 32    public async Task<Tenant> GetAsync(string id, CancellationToken cancellationToken = default)
 33    {
 034        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 035        return dictionary[id.EmptyIfNull()];
 036    }
 37
 38    public async Task<Tenant> GetAsync(TenantFilter filter, CancellationToken cancellationToken = default)
 39    {
 040        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 041        return filter.Apply(dictionary.Values.AsQueryable()).First();
 042    }
 43
 44    public async Task<IEnumerable<Tenant>> ListAsync(CancellationToken cancellationToken = default)
 45    {
 046        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 047        return dictionary.Values;
 048    }
 49
 50    public async Task<IEnumerable<Tenant>> ListAsync(TenantFilter filter, CancellationToken cancellationToken = default)
 51    {
 052        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 053        return filter.Apply(dictionary.Values.AsQueryable());
 054    }
 55
 56    public async Task ActivateTenantsAsync(CancellationToken cancellationToken = default)
 57    {
 758        await RefreshAsync(cancellationToken);
 759    }
 60
 61    public async Task DeactivateTenantsAsync(CancellationToken cancellationToken = default)
 62    {
 663        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 664        var tenants = dictionary.Values.ToArray();
 65
 1866        foreach (var tenant in tenants)
 367            await UnregisterTenantAsync(tenant, false, cancellationToken);
 668    }
 69
 70    public async Task RefreshAsync(CancellationToken cancellationToken = default)
 71    {
 772        await _refreshLock.WaitAsync(cancellationToken);
 73
 74        try
 75        {
 776            await using var scope = scopeFactory.CreateAsyncScope();
 777            var tenantsProvider = scope.ServiceProvider.GetRequiredService<ITenantsProvider>();
 778            var currentTenants = await GetTenantsDictionaryAsync(cancellationToken);
 779            var currentTenantIds = currentTenants.Keys;
 1480            var newTenants = (await tenantsProvider.ListAsync(cancellationToken)).ToDictionary(x => x.Id.EmptyIfNull());
 781            var newTenantIds = newTenants.Keys;
 782            var removedTenantIds = currentTenantIds.Except(newTenantIds).ToArray();
 783            var addedTenantIds = newTenantIds.Except(currentTenantIds).ToArray();
 84
 1485            foreach (var removedTenantId in removedTenantIds)
 86            {
 087                var removedTenant = currentTenants[removedTenantId];
 088                await UnregisterTenantAsync(removedTenant, true, cancellationToken);
 89            }
 90
 1491            foreach (var addedTenantId in addedTenantIds)
 92            {
 093                var addedTenant = newTenants[addedTenantId];
 094                await RegisterTenantAsync(addedTenant, cancellationToken);
 95            }
 796        }
 97        finally
 98        {
 799            _refreshLock.Release();
 100        }
 7101    }
 102
 103    private async Task<IDictionary<string, Tenant>> GetTenantsDictionaryAsync(CancellationToken cancellationToken)
 104    {
 13105        if (_tenantsDictionary == null)
 106        {
 7107            await _initializationLock.WaitAsync(cancellationToken); // Lock to ensure single-threaded initialization
 108            try
 109            {
 7110                if (_tenantsDictionary == null) // Double-check locking
 111                {
 7112                    _tenantsDictionary = new Dictionary<string, Tenant>();
 7113                    _tenantScopesDictionary = new Dictionary<Tenant, TenantScope>();
 7114                    var tenantsProvider = _serviceScope.ServiceProvider.GetRequiredService<ITenantsProvider>();
 7115                    var tenants = await tenantsProvider.ListAsync(cancellationToken);
 116
 28117                    foreach (var tenant in tenants)
 7118                        await RegisterTenantAsync(tenant, cancellationToken);
 119                }
 7120            }
 121            finally
 122            {
 7123                _initializationLock.Release();
 124            }
 125        }
 126
 13127        return _tenantsDictionary;
 13128    }
 129
 130    private async Task RegisterTenantAsync(Tenant tenant, CancellationToken cancellationToken = default)
 131    {
 7132        var scope = tenantScopeFactory.CreateScope(tenant);
 7133        _tenantsDictionary![tenant.Id.EmptyIfNull()] = tenant;
 7134        _tenantScopesDictionary![tenant] = scope;
 135
 7136        using (tenantAccessor.PushContext(tenant))
 7137            await tenantEvents.TenantActivatedAsync(new(tenant, scope, cancellationToken));
 7138    }
 139
 140    private async Task UnregisterTenantAsync(Tenant tenant, bool isDeleted, CancellationToken cancellationToken = defaul
 141    {
 3142        if (_tenantScopesDictionary!.Remove(tenant, out var scope))
 143        {
 3144            _tenantsDictionary!.Remove(tenant.Id.EmptyIfNull(), out _);
 145
 3146            using (tenantAccessor.PushContext(tenant))
 147            {
 3148                await tenantEvents.TenantDeactivatedAsync(new(tenant, scope, cancellationToken));
 149
 3150                if (isDeleted)
 0151                    await tenantEvents.TenantDeletedAsync(new(tenant, scope, cancellationToken));
 3152            }
 153        }
 3154    }
 155}