From 06b9907d85b6420410d1bc41462add773dba9753 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 26 Mar 2025 16:05:40 +0100 Subject: [PATCH] Fix DbContext pooling (#6531) * Enable DbContext pooling and refactor DI scopes Added support for using DbContext pooling with a configurable option. Refactored to use scoped service providers for better isolation and lifecycle management. Updated persistence feature base to integrate sensitive data logging and warning configurations. * Update package management and Elsa Studio version references Centralized package versioning remains enabled, while unused package references and conditional groups for specific .NET targets are removed for simplification. Elsa Studio version is upgraded from 3.3.0-rc4 to 3.4.0-rc1. * Simplify DbContextOptions setup logic Removed conditional compilation and unused code to streamline the DbContextOptions configuration. This improves maintainability and ensures consistent behavior across frameworks. Sensitive data logging is no longer explicitly enabled here. --- Directory.Build.props | 2 +- Directory.Packages.props | 418 +++++++----------- src/apps/Elsa.Server.Web/Program.cs | 20 +- .../ElsaDbContextBase.cs | 14 +- .../PersistenceFeatureBase.cs | 19 +- .../WorkflowManagementPersistenceFeature.cs | 22 + 6 files changed, 222 insertions(+), 273 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index eb59aca7f..7476459c8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -37,6 +37,6 @@ $(NoWarn);IL2026;IL2046;IL2057;IL2067;IL2070;IL2072;IL2075;IL2087;IL2091 - 3.3.0-rc4 + 3.4.0-rc1 \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 8ef10f029..a7a83a268 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,254 +1,168 @@ - - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs index f5ac0c5cf..b2d1941ce 100644 --- a/src/apps/Elsa.Server.Web/Program.cs +++ b/src/apps/Elsa.Server.Web/Program.cs @@ -84,6 +84,7 @@ using StackExchange.Redis; // ReSharper disable RedundantAssignment const PersistenceProvider persistenceProvider = PersistenceProvider.EntityFrameworkCore; +const bool useDbContextPooling = true; const bool useHangfire = false; const bool useQuartz = true; const bool useMassTransit = true; @@ -236,6 +237,8 @@ services else identity.UseEntityFrameworkCore(ef => { + ef.UseContextPooling = useDbContextPooling; + if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) ef.UseSqlServer(sqlServerConnectionString); else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) @@ -286,6 +289,7 @@ services else management.UseEntityFrameworkCore(ef => { + ef.UseContextPooling = useDbContextPooling; if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) ef.UseSqlServer(sqlServerConnectionString); else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) @@ -336,6 +340,7 @@ services else runtime.UseEntityFrameworkCore(ef => { + ef.UseContextPooling = useDbContextPooling; if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) { //ef.UseSqlServer(sqlServerConnectionString, new ElsaDbContextOptions); @@ -495,6 +500,7 @@ services { alterations.UseEntityFrameworkCore(ef => { + ef.UseContextPooling = useDbContextPooling; if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) ef.UseSqlServer(sqlServerConnectionString); else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) @@ -634,15 +640,20 @@ services management.ConfigureOptions(options => configuration.GetSection("Secrets:Management").Bind(options)); if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) management.UseEntityFrameworkCore(ef => - ef.UseSqlServer(sqlServerConnectionString) - ); + { + ef.UseContextPooling = useDbContextPooling; + ef.UseSqlServer(sqlServerConnectionString); + }); else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) management.UseEntityFrameworkCore(ef => - ef.UsePostgreSql(postgresConnectionString) - ); + { + ef.UseContextPooling = useDbContextPooling; + ef.UsePostgreSql(postgresConnectionString); + }); else management.UseEntityFrameworkCore(ef => { + ef.UseContextPooling = useDbContextPooling; ef.UseSqlite(sp => sp.GetSqliteConnectionString()); }); }) @@ -695,6 +706,7 @@ services { management.UseEntityFrameworkCore(ef => { + ef.UseContextPooling = useDbContextPooling; if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite) ef.UseSqlite(sqliteConnectionString); if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) ef.UseSqlServer(sqlServerConnectionString); if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString); diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextBase.cs b/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextBase.cs index 5b9b7557b..5329a7183 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextBase.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextBase.cs @@ -60,14 +60,6 @@ public abstract class ElsaDbContextBase : DbContext, IElsaDbContextSchema return await base.SaveChangesAsync(cancellationToken); } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.EnableSensitiveDataLogging(); -#if NET9_0_OR_GREATER - optionsBuilder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); -#endif - } - /// protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -78,7 +70,8 @@ public abstract class ElsaDbContextBase : DbContext, IElsaDbContextSchema additionalConfigurations?.Invoke(modelBuilder); - var entityTypeHandlers = ServiceProvider.GetServices().ToList(); + using var scope = ServiceProvider.CreateScope(); + var entityTypeHandlers = scope.ServiceProvider.GetServices().ToList(); foreach (var entityType in modelBuilder.Model.GetEntityTypes().ToList()) { @@ -89,7 +82,8 @@ public abstract class ElsaDbContextBase : DbContext, IElsaDbContextSchema private async Task OnBeforeSavingAsync(CancellationToken cancellationToken) { - var handlers = ServiceProvider.GetServices().ToList(); + using var scope = ServiceProvider.CreateScope(); + var handlers = scope.ServiceProvider.GetServices().ToList(); foreach (var entry in ChangeTracker.Entries().Where(IsModifiedEntity)) { foreach (var handler in handlers) diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs b/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs index 5df4f3a07..49fa20800 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs @@ -4,6 +4,7 @@ using Elsa.Features.Abstractions; using Elsa.Features.Attributes; using Elsa.Features.Services; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.DependencyInjection; namespace Elsa.EntityFrameworkCore; @@ -24,12 +25,12 @@ public abstract class PersistenceFeatureBase : FeatureBase /// /// Gets or sets a value indicating whether to use context pooling. /// - public bool UseContextPooling { get; set; } + public virtual bool UseContextPooling { get; set; } /// /// Gets or sets a value indicating whether to run migrations. /// - public bool RunMigrations { get; set; } = true; + public virtual bool RunMigrations { get; set; } = true; /// /// Gets or sets the lifetime of the . Defaults to . @@ -50,13 +51,19 @@ public abstract class PersistenceFeatureBase : FeatureBase /// public override void Apply() { - if(DbContextOptionsBuilder == null) + if (DbContextOptionsBuilder == null) throw new InvalidOperationException("The DbContextOptionsBuilder must be configured."); - + + Action setup = (sp, opts) => + { + opts.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); + DbContextOptionsBuilder(sp, opts); + }; + if (UseContextPooling) - Services.AddPooledDbContextFactory(DbContextOptionsBuilder); + Services.AddPooledDbContextFactory(setup); else - Services.AddDbContextFactory(DbContextOptionsBuilder, DbContextFactoryLifetime); + Services.AddDbContextFactory(setup, DbContextFactoryLifetime); } protected virtual void ConfigureMigrations() diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowManagementPersistenceFeature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowManagementPersistenceFeature.cs index 09e9e20b2..e89f9a299 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowManagementPersistenceFeature.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowManagementPersistenceFeature.cs @@ -24,4 +24,26 @@ public class WorkflowManagementPersistenceFeature(IModule module) : PersistenceF Module.Configure(x => x.DbContextOptionsBuilder = value); } } + + public override bool UseContextPooling + { + get => base.UseContextPooling; + set + { + base.UseContextPooling = value; + Module.Configure(x => x.UseContextPooling = value); + Module.Configure(x => x.UseContextPooling = value); + } + } + + public override bool RunMigrations + { + get => base.RunMigrations; + set + { + base.RunMigrations = value; + Module.Configure(x => x.RunMigrations = value); + Module.Configure(x => x.RunMigrations = value); + } + } } \ No newline at end of file