< Summary

Information
Class: ArturRios.Data.Relational.Core.DependencyInjection.ServiceCollectionExtensions
Assembly: ArturRios.Data.Relational.Core
File(s): D:\Repositories\dotnet-data\src\ArturRios.Data.Relational.Core\DependencyInjection\ServiceCollectionExtensions.cs
Line coverage
83%
Covered lines: 68
Uncovered lines: 13
Coverable lines: 81
Total lines: 175
Line coverage: 83.9%
Branch coverage
75%
Covered branches: 18
Total branches: 24
Branch coverage: 75%
Method coverage
83%
Covered methods: 5
Fully covered methods: 0
Total methods: 6
Method coverage: 83.3%
Full method coverage: 0%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
EnsureProviderRegistered(...)100%44100%
TryGetProviderType(...)66.66%6680%
ResolveProvider(...)50%3241.66%
AddDataConfigFromSettings(...)0%620%
AddDataConfig(...)100%22100%
AddDataConfigFromEnvironment(...)87.5%88100%

File(s)

D:\Repositories\dotnet-data\src\ArturRios.Data.Relational.Core\DependencyInjection\ServiceCollectionExtensions.cs

#LineLine coverage
 1using ArturRios.Data.Relational.Core.Configuration;
 2using ArturRios.Data.Relational.Core.Exceptions;
 3using ArturRios.Data.Relational.Core.Interfaces;
 4using ArturRios.Data.Relational.Core.Providers;
 5using ArturRios.Data.Relational.Core.Repositories;
 6using ArturRios.Data.Relational.Core.Transactions;
 7using Microsoft.Extensions.Configuration;
 8using Microsoft.Extensions.DependencyInjection;
 9
 10namespace ArturRios.Data.Relational.Core.DependencyInjection;
 11
 12/// <summary>
 13///     Dependency-injection registration for the ArturRios.Data.Relational.Core relational stack.
 14/// </summary>
 15public static class ServiceCollectionExtensions
 16{
 17    // Best-effort eager check: only throws when it can prove no provider matches.
 18    // Factory-registered providers (services.AddSingleton<IDatabaseProvider>(sp => ...)) expose
 19    // neither ImplementationInstance nor ImplementationType, so TryGetProviderType cannot inspect
 20    // them. Their presence means absence cannot be proven, so validation defers to ResolveProvider
 21    // at first use instead of failing fast on a false negative.
 22    private static void EnsureProviderRegistered(IServiceCollection services, DatabaseType type)
 423    {
 424        var providerTypes = services
 5125            .Where(d => d.ServiceType == typeof(IDatabaseProvider))
 426            .Select(TryGetProviderType)
 427            .ToList();
 28
 729        if (providerTypes.Any(t => t == type))
 230        {
 231            return;
 32        }
 33
 334        var hasUninspectableProvider = providerTypes.Any(t => t is null);
 35
 236        if (hasUninspectableProvider)
 137        {
 138            return;
 39        }
 40
 141        throw new DataAccessException(
 142        [
 143            $"No IDatabaseProvider registered for DatabaseType '{type}'. " +
 144            $"Install and register the matching provider package " +
 145            $"(e.g. ArturRios.Data.Core.{type}) by calling its Add{type}Provider() extension."
 146        ]);
 347    }
 48
 49    // Reads a registered provider's DatabaseType without building a ServiceProvider.
 50    // Providers are stateless with parameterless constructors, so instantiating to read Type is safe.
 51    private static DatabaseType? TryGetProviderType(ServiceDescriptor descriptor)
 352    {
 353        if (descriptor.ImplementationInstance is IDatabaseProvider instance)
 254        {
 255            return instance.Type;
 56        }
 57
 158        if (descriptor.ImplementationType is { } implementationType &&
 159            Activator.CreateInstance(implementationType) is IDatabaseProvider created)
 060        {
 061            return created.Type;
 62        }
 63
 164        return null;
 365    }
 66
 67    private static IDatabaseProvider ResolveProvider(
 68        IEnumerable<IDatabaseProvider> providers, DatabaseType type)
 369    {
 670        var match = providers.FirstOrDefault(p => p.Type == type);
 371        if (match is null)
 072        {
 073            throw new DataAccessException(
 074            [
 075                $"No IDatabaseProvider registered for DatabaseType '{type}'. " +
 076                $"Install and register the matching provider package " +
 077                $"(e.g. ArturRios.Data.Core.{type}) by calling its Add{type}Provider() extension."
 078            ]);
 79        }
 80
 381        return match;
 382    }
 83
 84    /// <param name="services">The service collection.</param>
 85    extension(IServiceCollection services)
 86    {
 87        /// <summary>
 88        ///     Registers the configured <typeparamref name="TContext" />, repositories, and unit of work,
 89        ///     binding options from the given configuration section.
 90        /// </summary>
 91        /// <param name="configuration">Application configuration.</param>
 92        /// <param name="sectionName">Configuration section holding the options.</param>
 93        /// <typeparam name="TContext">The application's context type.</typeparam>
 94        public IServiceCollection AddDataConfigFromSettings<TContext>(IConfiguration configuration,
 95            string sectionName)
 96            where TContext : BaseDbContext
 097        {
 098            var options = configuration.GetSection(sectionName).Get<BaseDbContextOptions>()
 099                          ?? new BaseDbContextOptions();
 100
 0101            return services.AddDataConfig<TContext>(options);
 102        }
 103
 104        /// <summary>
 105        ///     Registers the configured <typeparamref name="TContext" />, repositories, and unit of work
 106        ///     from an explicit options instance.
 107        /// </summary>
 108        /// <param name="options">The database options.</param>
 109        /// <typeparam name="TContext">The application's context type.</typeparam>
 110        public IServiceCollection AddDataConfig<TContext>(BaseDbContextOptions options)
 111            where TContext : BaseDbContext
 4112        {
 4113            services.AddDbContext<TContext>((sp, builder) =>
 3114            {
 3115                var provider = ResolveProvider(sp.GetServices<IDatabaseProvider>(), options.DatabaseType);
 3116                provider.Configure(builder, options.ConnectionString);
 7117            });
 118
 7119            services.AddScoped<BaseDbContext>(sp => sp.GetRequiredService<TContext>());
 120
 4121            services.AddScoped(typeof(IReadOnlyRepository<>), typeof(EfRepository<>));
 4122            services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
 4123            services.AddScoped(typeof(IAsyncReadOnlyRepository<>), typeof(EfRepository<>));
 4124            services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
 125
 4126            services.AddScoped<IUnitOfWork, EfUnitOfWork>();
 4127            services.AddScoped<IAsyncUnitOfWork, EfUnitOfWork>();
 128
 129            // Validate provider availability eagerly so misconfiguration fails at registration, not first use.
 4130            EnsureProviderRegistered(services, options.DatabaseType);
 131
 3132            return services;
 133        }
 134
 135        /// <summary>
 136        ///     Registers the configured <typeparamref name="TContext" />, repositories, and unit of work,
 137        ///     reading options entirely from environment variables. The appsettings section is not consulted.
 138        /// </summary>
 139        /// <param name="prefix">
 140        ///     Environment-variable name prefix. Variables are read as
 141        ///     <c>{prefix}_DATABASETYPE</c> and <c>{prefix}_CONNECTIONSTRING</c>.
 142        /// </param>
 143        /// <typeparam name="TContext">The application's context type.</typeparam>
 144        public IServiceCollection AddDataConfigFromEnvironment<TContext>(string prefix)
 145            where TContext : BaseDbContext
 8146        {
 8147            if (string.IsNullOrWhiteSpace(prefix))
 3148            {
 3149                throw new ArgumentException(
 3150                    "Environment-variable prefix must not be null or whitespace.", nameof(prefix));
 151            }
 152
 5153            var databaseTypeValue = Environment.GetEnvironmentVariable($"{prefix}_DATABASETYPE");
 154
 5155            if (!Enum.TryParse<DatabaseType>(databaseTypeValue, ignoreCase: true, out var databaseType) ||
 5156                !Enum.IsDefined(databaseType))
 4157            {
 4158                throw new DataAccessException(
 4159                [
 4160                    $"Environment variable '{prefix}_DATABASETYPE' is unset or not a valid DatabaseType. " +
 4161                    $"Allowed values: {string.Join(", ", Enum.GetNames<DatabaseType>())}."
 4162                ]);
 163            }
 164
 1165            var connectionString =
 1166                Environment.GetEnvironmentVariable($"{prefix}_CONNECTIONSTRING") ?? string.Empty;
 167
 1168            return services.AddDataConfig<TContext>(new BaseDbContextOptions
 1169            {
 1170                DatabaseType = databaseType,
 1171                ConnectionString = connectionString
 1172            });
 173        }
 174    }
 175}