From 3f6ebe7656d6ded634db2c268a4b60d11fa076c7 Mon Sep 17 00:00:00 2001 From: Nikolaj Brask-Nielsen Date: Thu, 24 Aug 2023 10:15:18 +0200 Subject: [PATCH] feat: Let the DbContext handle the connection to the database (#14674) --- ...mbracoEFCoreServiceCollectionExtensions.cs | 59 +++++-------------- .../UmbracoDbContext.cs | 46 ++++++++++++++- 2 files changed, 60 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs index 6d8c207635..da9c2e59ef 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs @@ -17,8 +17,6 @@ public static class UmbracoEFCoreServiceCollectionExtensions public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) where T : DbContext { - defaultEFCoreOptionsAction ??= DefaultOptionsAction; - services.AddPooledDbContextFactory((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); @@ -31,38 +29,9 @@ public static class UmbracoEFCoreServiceCollectionExtensions return services; } - private static void SetupDbContext(DefaultEFCoreOptionsAction defaultEFCoreOptionsAction, IServiceProvider provider, DbContextOptionsBuilder builder) - { - ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider); - IEnumerable migrationProviders = provider.GetServices(); - IMigrationProviderSetup? migrationProvider = - migrationProviders.FirstOrDefault(x => x.ProviderName == connectionStrings.ProviderName); - migrationProvider?.Setup(builder, connectionStrings.ConnectionString); - defaultEFCoreOptionsAction(builder, connectionStrings.ConnectionString, connectionStrings.ProviderName); - } - - private static ConnectionStrings GetConnectionStringAndProviderName(IServiceProvider serviceProvider) - { - string? connectionString = null; - string? providerName = null; - - ConnectionStrings connectionStrings = serviceProvider.GetRequiredService>().CurrentValue; - - // Replace data directory - string? dataDirectory = AppDomain.CurrentDomain.GetData(Constants.System.DataDirectoryName)?.ToString(); - if (string.IsNullOrEmpty(dataDirectory) is false) - { - connectionStrings.ConnectionString = connectionStrings.ConnectionString?.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); - } - - return connectionStrings; - } - public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, string connectionString, string providerName, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) where T : DbContext { - defaultEFCoreOptionsAction ??= DefaultOptionsAction; - // Replace data directory string? dataDirectory = AppDomain.CurrentDomain.GetData(Constants.System.DataDirectoryName)?.ToString(); if (string.IsNullOrEmpty(dataDirectory) is false) @@ -70,7 +39,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions connectionString = connectionString.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); } - services.AddPooledDbContextFactory((_, options) => defaultEFCoreOptionsAction(options, providerName, connectionString)); + services.AddPooledDbContextFactory(options => defaultEFCoreOptionsAction?.Invoke(options, providerName, connectionString)); services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); services.AddUnique, AmbientEFCoreScopeStack>(); @@ -82,21 +51,23 @@ public static class UmbracoEFCoreServiceCollectionExtensions return services; } - private static void DefaultOptionsAction(DbContextOptionsBuilder options, string? providerName, string? connectionString) + private static void SetupDbContext(DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction, IServiceProvider provider, DbContextOptionsBuilder builder) { - if (connectionString.IsNullOrWhiteSpace()) + ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider); + defaultEFCoreOptionsAction?.Invoke(builder, connectionStrings.ConnectionString, connectionStrings.ProviderName); + } + + private static ConnectionStrings GetConnectionStringAndProviderName(IServiceProvider serviceProvider) + { + ConnectionStrings connectionStrings = serviceProvider.GetRequiredService>().CurrentValue; + + // Replace data directory + string? dataDirectory = AppDomain.CurrentDomain.GetData(Constants.System.DataDirectoryName)?.ToString(); + if (string.IsNullOrEmpty(dataDirectory) is false) { - return; + connectionStrings.ConnectionString = connectionStrings.ConnectionString?.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); } - switch (providerName) - { - case "Microsoft.Data.Sqlite": - options.UseSqlite(connectionString); - break; - case "Microsoft.Data.SqlClient": - options.UseSqlServer(connectionString); - break; - } + return connectionStrings; } } diff --git a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs index 688d91ffa9..7874d9d889 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs @@ -1,9 +1,18 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Persistence.EFCore.Migrations; namespace Umbraco.Cms.Persistence.EFCore; +/// +/// Represents the Umbraco EF Core database context. +/// /// /// To autogenerate migrations use the following commands /// and insure the 'src/Umbraco.Web.UI/appsettings.json' have a connection string set with the right provider. @@ -23,9 +32,44 @@ namespace Umbraco.Cms.Persistence.EFCore; /// public class UmbracoDbContext : DbContext { + /// + /// Initializes a new instance of the class. + /// + /// public UmbracoDbContext(DbContextOptions options) - : base(options) + : base(ConfigureOptions(options, out IOptionsMonitor? connectionStringsOptionsMonitor)) { + connectionStringsOptionsMonitor.OnChange(c => + { + ILogger logger = StaticServiceProvider.Instance.GetRequiredService>(); + logger.LogWarning("Connection string changed, disposing context"); + Dispose(); + }); + } + + private static DbContextOptions ConfigureOptions(DbContextOptions options, out IOptionsMonitor connectionStringsOptionsMonitor) + { + connectionStringsOptionsMonitor = StaticServiceProvider.Instance.GetRequiredService>(); + + ConnectionStrings connectionStrings = connectionStringsOptionsMonitor.CurrentValue; + + if (string.IsNullOrWhiteSpace(connectionStrings.ConnectionString)) + { + ILogger logger = StaticServiceProvider.Instance.GetRequiredService>(); + logger.LogCritical("No connection string was found, cannot setup Umbraco EF Core context"); + } + + IEnumerable migrationProviders = StaticServiceProvider.Instance.GetServices(); + IMigrationProviderSetup? migrationProvider = migrationProviders.FirstOrDefault(x => x.ProviderName == connectionStrings.ProviderName); + + if (migrationProvider == null && connectionStrings.ProviderName != null) + { + throw new InvalidOperationException($"No migration provider found for provider name {connectionStrings.ProviderName}"); + } + + var optionsBuilder = new DbContextOptionsBuilder(options); + migrationProvider?.Setup(optionsBuilder, connectionStrings.ConnectionString); + return optionsBuilder.Options; } protected override void OnModelCreating(ModelBuilder modelBuilder)