Bugfix/19601 can not add ef core migrations (#19846)
* fix EFCore add migration issue * update test * Resolved breaking changes and code review comments. * Removed extra line break. --------- Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
@@ -19,7 +19,7 @@ public class UmbracoEFCoreComposer : IComposer
|
||||
builder.AddNotificationAsyncHandler<DatabaseSchemaAndDataCreatedNotification, EFCoreCreateTablesNotificationHandler>();
|
||||
builder.AddNotificationAsyncHandler<UnattendedInstallNotification, EFCoreCreateTablesNotificationHandler>();
|
||||
|
||||
builder.Services.AddUmbracoDbContext<UmbracoDbContext>((options) =>
|
||||
builder.Services.AddUmbracoDbContext<UmbracoDbContext>((provider, options, connectionString, providerName) =>
|
||||
{
|
||||
// Register the entity sets needed by OpenIddict.
|
||||
options.UseOpenIddict();
|
||||
|
||||
@@ -17,32 +17,50 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
||||
/// <summary>
|
||||
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="optionsAction"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddUmbracoDbContext<T>(this IServiceCollection services, Action<DbContextOptionsBuilder>? optionsAction = null)
|
||||
[Obsolete("Please use the method overload that takes all parameters for the optionsAction. Scheduled for removal in Umbraco 18.")]
|
||||
public static IServiceCollection AddUmbracoDbContext<T>(
|
||||
this IServiceCollection services,
|
||||
Action<DbContextOptionsBuilder>? optionsAction = null)
|
||||
where T : DbContext
|
||||
=> AddUmbracoDbContext<T>(services, (sp, optionsBuilder, connectionString, providerName) => optionsAction?.Invoke(optionsBuilder));
|
||||
|
||||
/// <summary>
|
||||
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddUmbracoDbContext<T>(
|
||||
this IServiceCollection services,
|
||||
Action<DbContextOptionsBuilder, string?, string?, IServiceProvider?>? optionsAction = null)
|
||||
where T : DbContext
|
||||
{
|
||||
return AddUmbracoDbContext<T>(services, (IServiceProvider _, DbContextOptionsBuilder options) =>
|
||||
return AddUmbracoDbContext<T>(services, (IServiceProvider provider, DbContextOptionsBuilder optionsBuilder, string? providerName, string? connectionString) =>
|
||||
{
|
||||
optionsAction?.Invoke(options);
|
||||
ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider);
|
||||
optionsAction?.Invoke(optionsBuilder, connectionStrings.ConnectionString, connectionStrings.ProviderName, provider);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="optionsAction"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddUmbracoDbContext<T>(this IServiceCollection services, Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction = null)
|
||||
[Obsolete("Please use the method overload that takes all parameters for the optionsAction. Scheduled for removal in Umbraco 18.")]
|
||||
public static IServiceCollection AddUmbracoDbContext<T>(
|
||||
this IServiceCollection services,
|
||||
Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction = null)
|
||||
where T : DbContext
|
||||
=> AddUmbracoDbContext<T>(services, (sp, optionsBuilder, connectionString, providerName) => optionsAction?.Invoke(sp, optionsBuilder));
|
||||
|
||||
/// <summary>
|
||||
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddUmbracoDbContext<T>(
|
||||
this IServiceCollection services,
|
||||
Action<IServiceProvider, DbContextOptionsBuilder, string?, string?>? optionsAction = null)
|
||||
where T : DbContext
|
||||
{
|
||||
optionsAction ??= (sp, options) => { };
|
||||
optionsAction ??= (sp, optionsBuilder, connectionString, providerName) => { };
|
||||
|
||||
services.AddPooledDbContextFactory<T>(optionsAction);
|
||||
|
||||
services.AddPooledDbContextFactory<T>((provider, optionsBuilder) => SetupDbContext(optionsAction, provider, optionsBuilder));
|
||||
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
|
||||
|
||||
services.AddUnique<IAmbientEFCoreScopeStack<T>, AmbientEFCoreScopeStack<T>>();
|
||||
@@ -110,4 +128,25 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
||||
|
||||
builder.UseDatabaseProvider(connectionStrings.ProviderName, connectionStrings.ConnectionString);
|
||||
}
|
||||
|
||||
private static void SetupDbContext(Action<IServiceProvider, DbContextOptionsBuilder, string?, string?>? optionsAction, IServiceProvider provider, DbContextOptionsBuilder builder)
|
||||
{
|
||||
ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider);
|
||||
|
||||
optionsAction?.Invoke(provider, builder, connectionStrings.ConnectionString, connectionStrings.ProviderName);
|
||||
}
|
||||
|
||||
private static ConnectionStrings GetConnectionStringAndProviderName(IServiceProvider serviceProvider)
|
||||
{
|
||||
ConnectionStrings connectionStrings = serviceProvider.GetRequiredService<IOptionsMonitor<ConnectionStrings>>().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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Configuration;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -17,14 +18,14 @@ namespace Umbraco.Cms.Persistence.EFCore;
|
||||
/// and insure the 'src/Umbraco.Web.UI/appsettings.json' have a connection string set with the right provider.
|
||||
///
|
||||
/// Create a migration for each provider.
|
||||
/// <code>dotnet ef migrations add %Name% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer -c UmbracoDbContext -- --provider SqlServer</code>
|
||||
/// <code>dotnet ef migrations add %Name% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer -c UmbracoDbContext</code>
|
||||
///
|
||||
/// <code>dotnet ef migrations add %Name% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite -c UmbracoDbContext -- --provider Sqlite</code>
|
||||
/// <code>dotnet ef migrations add %Name% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite -c UmbracoDbContext</code>
|
||||
///
|
||||
/// Remove the last migration for each provider.
|
||||
/// <code>dotnet ef migrations remove -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer -- --provider SqlServer</code>
|
||||
/// <code>dotnet ef migrations remove -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer</code>
|
||||
///
|
||||
/// <code>dotnet ef migrations remove -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite -- --provider Sqlite</code>
|
||||
/// <code>dotnet ef migrations remove -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite</code>
|
||||
///
|
||||
/// To find documentation about this way of working with the context see
|
||||
/// https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/providers?tabs=dotnet-core-cli#using-one-context-type
|
||||
@@ -37,28 +38,35 @@ public class UmbracoDbContext : DbContext
|
||||
/// <param name="options"></param>
|
||||
public UmbracoDbContext(DbContextOptions<UmbracoDbContext> options)
|
||||
: base(ConfigureOptions(options))
|
||||
{
|
||||
|
||||
}
|
||||
{ }
|
||||
|
||||
private static DbContextOptions<UmbracoDbContext> ConfigureOptions(DbContextOptions<UmbracoDbContext> options)
|
||||
{
|
||||
IOptionsMonitor<ConnectionStrings> connectionStringsOptionsMonitor = StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<ConnectionStrings>>();
|
||||
|
||||
ConnectionStrings connectionStrings = connectionStringsOptionsMonitor.CurrentValue;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionStrings.ConnectionString))
|
||||
var extensions = options.Extensions.FirstOrDefault() as Microsoft.EntityFrameworkCore.Infrastructure.CoreOptionsExtension;
|
||||
IServiceProvider? serviceProvider = extensions?.ApplicationServiceProvider;
|
||||
serviceProvider ??= StaticServiceProvider.Instance;
|
||||
if (serviceProvider == null)
|
||||
{
|
||||
ILogger<UmbracoDbContext> logger = StaticServiceProvider.Instance.GetRequiredService<ILogger<UmbracoDbContext>>();
|
||||
logger.LogCritical("No connection string was found, cannot setup Umbraco EF Core context");
|
||||
// If the service provider is null, we cannot resolve the connection string or migration provider.
|
||||
throw new InvalidOperationException("The service provider is not configured. Ensure that UmbracoDbContext is registered correctly.");
|
||||
}
|
||||
|
||||
IOptionsMonitor<ConnectionStrings>? connectionStringsOptionsMonitor = serviceProvider?.GetRequiredService<IOptionsMonitor<ConnectionStrings>>();
|
||||
|
||||
ConnectionStrings? connectionStrings = connectionStringsOptionsMonitor?.CurrentValue;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionStrings?.ConnectionString))
|
||||
{
|
||||
ILogger<UmbracoDbContext>? logger = serviceProvider?.GetRequiredService<ILogger<UmbracoDbContext>>();
|
||||
logger?.LogCritical("No connection string was found, cannot setup Umbraco EF Core context");
|
||||
|
||||
// we're throwing an exception here to make it abundantly clear that one should never utilize (or have a
|
||||
// dependency on) the DbContext before the connection string has been initialized by the installer.
|
||||
throw new InvalidOperationException("No connection string was found, cannot setup Umbraco EF Core context");
|
||||
}
|
||||
|
||||
IEnumerable<IMigrationProviderSetup> migrationProviders = StaticServiceProvider.Instance.GetServices<IMigrationProviderSetup>();
|
||||
IMigrationProviderSetup? migrationProvider = migrationProviders.FirstOrDefault(x => x.ProviderName.CompareProviderNames(connectionStrings.ProviderName));
|
||||
IEnumerable<IMigrationProviderSetup>? migrationProviders = serviceProvider?.GetServices<IMigrationProviderSetup>();
|
||||
IMigrationProviderSetup? migrationProvider = migrationProviders?.FirstOrDefault(x => x.ProviderName.CompareProviderNames(connectionStrings.ProviderName));
|
||||
|
||||
if (migrationProvider == null && connectionStrings.ProviderName != null)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ internal sealed class CustomDbContextUmbracoProviderTests : UmbracoIntegrationTe
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options) =>
|
||||
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options, connectionString, providerName) =>
|
||||
{
|
||||
options.UseUmbracoDatabaseProvider(serviceProvider);
|
||||
});
|
||||
@@ -53,7 +53,7 @@ public class CustomDbContextCustomSqliteProviderTests : UmbracoIntegrationTest
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options) =>
|
||||
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options, connectionString, providerName) =>
|
||||
{
|
||||
options.UseSqlite("Data Source=:memory:;Version=3;New=True;");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user