< 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
85%
Covered lines: 70
Uncovered lines: 12
Coverable lines: 82
Total lines: 160
Line coverage: 85.3%
Branch coverage
96%
Covered branches: 27
Total branches: 28
Branch coverage: 96.4%
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()50%22100%
FindAsync()100%210%
GetAsync()100%210%
GetAsync()100%210%
ListAsync()100%11100%
ListAsync()100%210%
ActivateTenantsAsync()100%11100%
DeactivateTenantsAsync()100%22100%
RefreshAsync()100%1010100%
GetTenantsDictionaryAsync()100%1010100%
RegisterTenantAsync()100%11100%
UnregisterTenantAsync()100%44100%

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    {
 1116        await _serviceScope.DisposeAsync();
 1117        _initializationLock.Dispose();
 1118    }
 19
 20    public async Task<Tenant?> FindAsync(string id, CancellationToken cancellationToken = default)
 21    {
 5022        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 5023        return dictionary.TryGetValue(id.EmptyIfNull(), out var tenant) ? tenant : null;
 5024    }
 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    {
 546        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 547        return dictionary.Values;
 548    }
 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    {
 658        await RefreshAsync(cancellationToken);
 659    }
 60
 61    public async Task DeactivateTenantsAsync(CancellationToken cancellationToken = default)
 62    {
 663        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 664        var tenants = dictionary.Values.ToArray();
 65
 3666        foreach (var tenant in tenants)
 1267            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;
 780            var tenantsFromProvider = (await tenantsProvider.ListAsync(cancellationToken)).ToList();
 781            var newTenants = tenantsFromProvider.Count == 0
 782                ? new Dictionary<string, Tenant> { [Tenant.DefaultTenantId] = Tenant.Default }
 2283                : tenantsFromProvider.ToDictionary(x => x.Id.EmptyIfNull());
 784            var newTenantIds = newTenants.Keys;
 785            var removedTenantIds = currentTenantIds.Except(newTenantIds).ToArray();
 786            var addedTenantIds = newTenantIds.Except(currentTenantIds).ToArray();
 87
 1688            foreach (var removedTenantId in removedTenantIds)
 89            {
 190                var removedTenant = currentTenants[removedTenantId];
 191                await UnregisterTenantAsync(removedTenant, true, cancellationToken);
 92            }
 93
 1694            foreach (var addedTenantId in addedTenantIds)
 95            {
 196                var addedTenant = newTenants[addedTenantId];
 197                await RegisterTenantAsync(addedTenant, cancellationToken);
 98            }
 799        }
 100        finally
 101        {
 7102            _refreshLock.Release();
 103        }
 7104    }
 105
 106    private async Task<IDictionary<string, Tenant>> GetTenantsDictionaryAsync(CancellationToken cancellationToken)
 107    {
 68108        if (_tenantsDictionary == null)
 109        {
 7110            await _initializationLock.WaitAsync(cancellationToken); // Lock to ensure single-threaded initialization
 111            try
 112            {
 7113                if (_tenantsDictionary == null) // Double-check locking
 114                {
 7115                    _tenantsDictionary = new Dictionary<string, Tenant>();
 7116                    _tenantScopesDictionary = new Dictionary<Tenant, TenantScope>();
 7117                    var tenantsProvider = _serviceScope.ServiceProvider.GetRequiredService<ITenantsProvider>();
 7118                    var tenants = (await tenantsProvider.ListAsync(cancellationToken)).ToList();
 7119                    if (tenants.Count == 0)
 2120                        tenants = [Tenant.Default];
 121
 48122                    foreach (var tenant in tenants)
 17123                        await RegisterTenantAsync(tenant, cancellationToken);
 124                }
 7125            }
 126            finally
 127            {
 7128                _initializationLock.Release();
 129            }
 130        }
 131
 68132        return _tenantsDictionary;
 68133    }
 134
 135    private async Task RegisterTenantAsync(Tenant tenant, CancellationToken cancellationToken = default)
 136    {
 18137        var scope = tenantScopeFactory.CreateScope(tenant);
 18138        _tenantsDictionary![tenant.Id.EmptyIfNull()] = tenant;
 18139        _tenantScopesDictionary![tenant] = scope;
 140
 18141        using (tenantAccessor.PushContext(tenant))
 18142            await tenantEvents.TenantActivatedAsync(new(tenant, scope, cancellationToken));
 18143    }
 144
 145    private async Task UnregisterTenantAsync(Tenant tenant, bool isDeleted, CancellationToken cancellationToken = defaul
 146    {
 13147        if (_tenantScopesDictionary!.Remove(tenant, out var scope))
 148        {
 13149            _tenantsDictionary!.Remove(tenant.Id.EmptyIfNull(), out _);
 150
 13151            using (tenantAccessor.PushContext(tenant))
 152            {
 13153                await tenantEvents.TenantDeactivatedAsync(new(tenant, scope, cancellationToken));
 154
 13155                if (isDeleted)
 1156                    await tenantEvents.TenantDeletedAsync(new(tenant, scope, cancellationToken));
 13157            }
 158        }
 13159    }
 160}