< 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
 16public class DefaultTenantService(IServiceScopeFactory scopeFactory, ITenantScopeFactory tenantScopeFactory, TenantEvent
 7{
 18    private readonly AsyncServiceScope _serviceScope = scopeFactory.CreateAsyncScope();
 19    private readonly SemaphoreSlim _initializationLock = new(1, 1);
 110    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    {
 116        await _serviceScope.DisposeAsync();
 117        _initializationLock.Dispose();
 118    }
 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    {
 158        await RefreshAsync(cancellationToken);
 159    }
 60
 61    public async Task DeactivateTenantsAsync(CancellationToken cancellationToken = default)
 62    {
 263        var dictionary = await GetTenantsDictionaryAsync(cancellationToken);
 264        var tenants = dictionary.Values.ToArray();
 65
 666        foreach (var tenant in tenants)
 167            await UnregisterTenantAsync(tenant, false, cancellationToken);
 268    }
 69
 70    public async Task RefreshAsync(CancellationToken cancellationToken = default)
 71    {
 172        await _refreshLock.WaitAsync(cancellationToken);
 73
 74        try
 75        {
 176            await using var scope = scopeFactory.CreateAsyncScope();
 177            var tenantsProvider = scope.ServiceProvider.GetRequiredService<ITenantsProvider>();
 178            var currentTenants = await GetTenantsDictionaryAsync(cancellationToken);
 179            var currentTenantIds = currentTenants.Keys;
 280            var newTenants = (await tenantsProvider.ListAsync(cancellationToken)).ToDictionary(x => x.Id.EmptyIfNull());
 181            var newTenantIds = newTenants.Keys;
 182            var removedTenantIds = currentTenantIds.Except(newTenantIds).ToArray();
 183            var addedTenantIds = newTenantIds.Except(currentTenantIds).ToArray();
 84
 285            foreach (var removedTenantId in removedTenantIds)
 86            {
 087                var removedTenant = currentTenants[removedTenantId];
 088                await UnregisterTenantAsync(removedTenant, true, cancellationToken);
 89            }
 90
 291            foreach (var addedTenantId in addedTenantIds)
 92            {
 093                var addedTenant = newTenants[addedTenantId];
 094                await RegisterTenantAsync(addedTenant, cancellationToken);
 95            }
 196        }
 97        finally
 98        {
 199            _refreshLock.Release();
 100        }
 1101    }
 102
 103    private async Task<IDictionary<string, Tenant>> GetTenantsDictionaryAsync(CancellationToken cancellationToken)
 104    {
 3105        if (_tenantsDictionary == null)
 106        {
 1107            await _initializationLock.WaitAsync(cancellationToken); // Lock to ensure single-threaded initialization
 108            try
 109            {
 1110                if (_tenantsDictionary == null) // Double-check locking
 111                {
 1112                    _tenantsDictionary = new Dictionary<string, Tenant>();
 1113                    _tenantScopesDictionary = new Dictionary<Tenant, TenantScope>();
 1114                    var tenantsProvider = _serviceScope.ServiceProvider.GetRequiredService<ITenantsProvider>();
 1115                    var tenants = await tenantsProvider.ListAsync(cancellationToken);
 116
 4117                    foreach (var tenant in tenants)
 1118                        await RegisterTenantAsync(tenant, cancellationToken);
 119                }
 1120            }
 121            finally
 122            {
 1123                _initializationLock.Release();
 124            }
 125        }
 126
 3127        return _tenantsDictionary;
 3128    }
 129
 130    private async Task RegisterTenantAsync(Tenant tenant, CancellationToken cancellationToken = default)
 131    {
 1132        var scope = tenantScopeFactory.CreateScope(tenant);
 1133        _tenantsDictionary![tenant.Id.EmptyIfNull()] = tenant;
 1134        _tenantScopesDictionary![tenant] = scope;
 135
 1136        using (tenantAccessor.PushContext(tenant))
 1137            await tenantEvents.TenantActivatedAsync(new(tenant, scope, cancellationToken));
 1138    }
 139
 140    private async Task UnregisterTenantAsync(Tenant tenant, bool isDeleted, CancellationToken cancellationToken = defaul
 141    {
 1142        if (_tenantScopesDictionary!.Remove(tenant, out var scope))
 143        {
 1144            _tenantsDictionary!.Remove(tenant.Id.EmptyIfNull(), out _);
 145
 1146            using (tenantAccessor.PushContext(tenant))
 147            {
 1148                await tenantEvents.TenantDeactivatedAsync(new(tenant, scope, cancellationToken));
 149
 1150                if (isDeleted)
 0151                    await tenantEvents.TenantDeletedAsync(new(tenant, scope, cancellationToken));
 1152            }
 153        }
 1154    }
 155}