using System.Reflection; using CShells.Features; using Elsa.Common.Entities; using Elsa.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; // ReSharper disable once CheckNamespace namespace Elsa.Persistence.EFCore; public abstract class PersistenceShellFeatureBase : IShellFeature where TDbContext : DbContext { /// /// Gets or sets a value indicating whether to use context pooling. /// When not explicitly set, falls back to shared settings if available. /// public bool? UseContextPooling { get; set; } /// /// Gets or sets a value indicating whether to run migrations. /// When not explicitly set, falls back to shared settings if available, defaulting to true. /// public bool? RunMigrations { get; set; } /// /// Gets or sets the lifetime of the . /// When not explicitly set, falls back to shared settings if available, defaulting to . /// public ServiceLifetime? DbContextFactoryLifetime { get; set; } /// /// Gets or sets the connection string to use for the database. /// When not explicitly set, falls back to shared settings if available. /// public string? ConnectionString { get; set; } /// /// Gets or sets additional options to configure the database context. /// When not explicitly set, falls back to shared settings if available. /// public ElsaDbContextOptions? DbContextOptions { get; set; } /// /// Gets or sets the callback used to configure the . /// protected virtual Action DbContextOptionsBuilder { get; set; } = (_, _) => { }; public void ConfigureServices(IServiceCollection services) { // Capture feature-specific settings var featureConnectionString = ConnectionString; var featureDbContextOptions = DbContextOptions; var featureUseContextPooling = UseContextPooling; var featureRunMigrations = RunMigrations; var featureDbContextFactoryLifetime = DbContextFactoryLifetime; // Resolve effective settings at runtime, falling back to shared settings Action setup = (sp, opts) => { var sharedSettings = sp.GetService>()?.Value; var connectionString = featureConnectionString ?? sharedSettings?.ConnectionString ?? throw new InvalidOperationException( $"Connection string not configured for {GetType().Name}. " + $"Either configure the feature directly or provide shared settings via the combined persistence feature."); var dbContextOptions = featureDbContextOptions ?? sharedSettings?.DbContextOptions; opts.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); // Configure the database provider var migrationsAssembly = GetMigrationsAssembly(); ConfigureProvider(opts, migrationsAssembly, connectionString, dbContextOptions); // Allow derived classes to further configure DbContextOptionsBuilder(sp, opts); }; // Resolve pooling and lifetime settings with fallback // Note: These are resolved at configuration time, not runtime, but they'll use defaults if not set var useContextPooling = featureUseContextPooling ?? false; var dbContextFactoryLifetime = featureDbContextFactoryLifetime ?? ServiceLifetime.Scoped; var runMigrations = featureRunMigrations ?? true; if (useContextPooling) services.AddPooledDbContextFactory(setup); else services.AddDbContextFactory(setup, dbContextFactoryLifetime); services.Decorate, TenantAwareDbContextFactory>(); services.Configure(options => { options.RunMigrations[typeof(TDbContext)] = runMigrations; }); services.AddStartupTask>(); OnConfiguring(services); } /// /// Gets the assembly containing migrations for this provider. /// By default, returns the assembly of the concrete feature type. /// protected virtual Assembly GetMigrationsAssembly() => GetType().Assembly; /// /// Configures the database provider for the specified . /// /// The options builder to configure. /// The assembly containing migrations. /// The connection string to use. /// Additional options to configure the database context. protected abstract void ConfigureProvider( DbContextOptionsBuilder builder, Assembly migrationsAssembly, string connectionString, ElsaDbContextOptions? options); protected virtual void OnConfiguring(IServiceCollection services) { } /// /// Adds a store to the service collection. /// /// The type of the entity. /// The type of the store. protected void AddStore(IServiceCollection services) where TEntity : class, new() where TStore : class { services .AddScoped>() .AddScoped() ; } /// /// Adds an entity store to the service collection. /// /// The type of the entity. /// The type of the store. protected void AddEntityStore(IServiceCollection services) where TEntity : Entity, new() where TStore : class { services .AddScoped>() .AddScoped() ; } }