< Summary

Information
Class: Elsa.Persistence.EFCore.PersistenceShellFeatureBase<T>
Assembly: Elsa.Persistence.EFCore.Common
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Persistence.EFCore.Common/PersistenceShellFeatureBase.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 58
Coverable lines: 58
Total lines: 153
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 16
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_UseContextPooling()100%210%
get_RunMigrations()100%210%
get_DbContextFactoryLifetime()100%210%
get_ConnectionString()100%210%
get_DbContextOptions()100%210%
get_DbContextOptionsBuilder()100%210%
ConfigureServices(...)0%272160%
GetMigrationsAssembly()100%210%
OnConfiguring(...)100%210%
AddStore(...)100%210%
AddEntityStore(...)100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Persistence.EFCore.Common/PersistenceShellFeatureBase.cs

#LineLine coverage
 1using System.Reflection;
 2using CShells.Features;
 3using Elsa.Common.Entities;
 4using Elsa.Extensions;
 5using Microsoft.EntityFrameworkCore;
 6using Microsoft.EntityFrameworkCore.Diagnostics;
 7using Microsoft.Extensions.DependencyInjection;
 8using Microsoft.Extensions.Options;
 9
 10// ReSharper disable once CheckNamespace
 11namespace Elsa.Persistence.EFCore;
 12
 13public abstract class PersistenceShellFeatureBase<TDbContext> : IShellFeature
 14    where TDbContext : DbContext
 15{
 16    /// <summary>
 17    /// Gets or sets a value indicating whether to use context pooling.
 18    /// When not explicitly set, falls back to shared settings if available.
 19    /// </summary>
 020    public bool? UseContextPooling { get; set; }
 21
 22    /// <summary>
 23    /// Gets or sets a value indicating whether to run migrations.
 24    /// When not explicitly set, falls back to shared settings if available, defaulting to true.
 25    /// </summary>
 026    public bool? RunMigrations { get; set; }
 27
 28    /// <summary>
 29    /// Gets or sets the lifetime of the <see cref="IDbContextFactory{TContext}"/>.
 30    /// When not explicitly set, falls back to shared settings if available, defaulting to <see cref="ServiceLifetime.Sc
 31    /// </summary>
 032    public ServiceLifetime? DbContextFactoryLifetime { get; set; }
 33
 34    /// <summary>
 35    /// Gets or sets the connection string to use for the database.
 36    /// When not explicitly set, falls back to shared settings if available.
 37    /// </summary>
 038    public string? ConnectionString { get; set; }
 39
 40    /// <summary>
 41    /// Gets or sets additional options to configure the database context.
 42    /// When not explicitly set, falls back to shared settings if available.
 43    /// </summary>
 044    public ElsaDbContextOptions? DbContextOptions { get; set; }
 45
 46    /// <summary>
 47    /// Gets or sets the callback used to configure the <see cref="DbContextOptionsBuilder"/>.
 48    /// </summary>
 049    protected virtual Action<IServiceProvider, DbContextOptionsBuilder> DbContextOptionsBuilder { get; set; } = (_, _) =
 50
 51    public void ConfigureServices(IServiceCollection services)
 52    {
 53        // Capture feature-specific settings
 054        var featureConnectionString = ConnectionString;
 055        var featureDbContextOptions = DbContextOptions;
 056        var featureUseContextPooling = UseContextPooling;
 057        var featureRunMigrations = RunMigrations;
 058        var featureDbContextFactoryLifetime = DbContextFactoryLifetime;
 59
 60        // Resolve effective settings at runtime, falling back to shared settings
 061        Action<IServiceProvider, DbContextOptionsBuilder> setup = (sp, opts) =>
 062        {
 063            var sharedSettings = sp.GetService<IOptions<SharedPersistenceSettings>>()?.Value;
 064
 065            var connectionString = featureConnectionString
 066                ?? sharedSettings?.ConnectionString
 067                ?? throw new InvalidOperationException(
 068                    $"Connection string not configured for {GetType().Name}. " +
 069                    $"Either configure the feature directly or provide shared settings via the combined persistence feat
 070
 071            var dbContextOptions = featureDbContextOptions ?? sharedSettings?.DbContextOptions;
 072
 073            opts.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
 074
 075            // Configure the database provider
 076            var migrationsAssembly = GetMigrationsAssembly();
 077            ConfigureProvider(opts, migrationsAssembly, connectionString, dbContextOptions);
 078
 079            // Allow derived classes to further configure
 080            DbContextOptionsBuilder(sp, opts);
 081        };
 82
 83        // Resolve pooling and lifetime settings with fallback
 84        // Note: These are resolved at configuration time, not runtime, but they'll use defaults if not set
 085        var useContextPooling = featureUseContextPooling ?? false;
 086        var dbContextFactoryLifetime = featureDbContextFactoryLifetime ?? ServiceLifetime.Scoped;
 087        var runMigrations = featureRunMigrations ?? true;
 88
 089        if (useContextPooling)
 090            services.AddPooledDbContextFactory<TDbContext>(setup);
 91        else
 092            services.AddDbContextFactory<TDbContext>(setup, dbContextFactoryLifetime);
 93
 094        services.Decorate<IDbContextFactory<TDbContext>, TenantAwareDbContextFactory<TDbContext>>();
 95
 096        services.Configure<MigrationOptions>(options =>
 097        {
 098            options.RunMigrations[typeof(TDbContext)] = runMigrations;
 099        });
 100
 0101        services.AddStartupTask<RunMigrationsStartupTask<TDbContext>>();
 0102        OnConfiguring(services);
 0103    }
 104
 105    /// <summary>
 106    /// Gets the assembly containing migrations for this provider.
 107    /// By default, returns the assembly of the concrete feature type.
 108    /// </summary>
 0109    protected virtual Assembly GetMigrationsAssembly() => GetType().Assembly;
 110
 111    /// <summary>
 112    /// Configures the database provider for the specified <see cref="DbContextOptionsBuilder"/>.
 113    /// </summary>
 114    /// <param name="builder">The options builder to configure.</param>
 115    /// <param name="migrationsAssembly">The assembly containing migrations.</param>
 116    /// <param name="connectionString">The connection string to use.</param>
 117    /// <param name="options">Additional options to configure the database context.</param>
 118    protected abstract void ConfigureProvider(
 119        DbContextOptionsBuilder builder,
 120        Assembly migrationsAssembly,
 121        string connectionString,
 122        ElsaDbContextOptions? options);
 123
 124    protected virtual void OnConfiguring(IServiceCollection services)
 125    {
 0126    }
 127
 128    /// <summary>
 129    /// Adds a store to the service collection.
 130    /// </summary>
 131    /// <typeparam name="TEntity">The type of the entity.</typeparam>
 132    /// <typeparam name="TStore">The type of the store.</typeparam>
 133    protected void AddStore<TEntity, TStore>(IServiceCollection services) where TEntity : class, new() where TStore : cl
 134    {
 0135        services
 0136            .AddScoped<Store<TDbContext, TEntity>>()
 0137            .AddScoped<TStore>()
 0138            ;
 0139    }
 140
 141    /// <summary>
 142    /// Adds an entity store to the service collection.
 143    /// </summary>
 144    /// <typeparam name="TEntity">The type of the entity.</typeparam>
 145    /// <typeparam name="TStore">The type of the store.</typeparam>
 146    protected void AddEntityStore<TEntity, TStore>(IServiceCollection services) where TEntity : Entity, new() where TSto
 147    {
 0148        services
 0149            .AddScoped<EntityStore<TDbContext, TEntity>>()
 0150            .AddScoped<TStore>()
 0151            ;
 0152    }
 153}