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<DatabaseSchemaAndDataCreatedNotification, EFCoreCreateTablesNotificationHandler>();
|
||||||
builder.AddNotificationAsyncHandler<UnattendedInstallNotification, 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.
|
// Register the entity sets needed by OpenIddict.
|
||||||
options.UseOpenIddict();
|
options.UseOpenIddict();
|
||||||
|
|||||||
@@ -17,32 +17,50 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
|
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
[Obsolete("Please use the method overload that takes all parameters for the optionsAction. Scheduled for removal in Umbraco 18.")]
|
||||||
/// <param name="services"></param>
|
public static IServiceCollection AddUmbracoDbContext<T>(
|
||||||
/// <param name="optionsAction"></param>
|
this IServiceCollection services,
|
||||||
/// <returns></returns>
|
Action<DbContextOptionsBuilder>? optionsAction = null)
|
||||||
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
|
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>
|
/// <summary>
|
||||||
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
|
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
[Obsolete("Please use the method overload that takes all parameters for the optionsAction. Scheduled for removal in Umbraco 18.")]
|
||||||
/// <param name="services"></param>
|
public static IServiceCollection AddUmbracoDbContext<T>(
|
||||||
/// <param name="optionsAction"></param>
|
this IServiceCollection services,
|
||||||
/// <returns></returns>
|
Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction = null)
|
||||||
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
|
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.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
|
||||||
|
|
||||||
services.AddUnique<IAmbientEFCoreScopeStack<T>, AmbientEFCoreScopeStack<T>>();
|
services.AddUnique<IAmbientEFCoreScopeStack<T>, AmbientEFCoreScopeStack<T>>();
|
||||||
@@ -110,4 +128,25 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
|||||||
|
|
||||||
builder.UseDatabaseProvider(connectionStrings.ProviderName, connectionStrings.ConnectionString);
|
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.Configuration;
|
||||||
|
using System.Diagnostics;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
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.
|
/// and insure the 'src/Umbraco.Web.UI/appsettings.json' have a connection string set with the right provider.
|
||||||
///
|
///
|
||||||
/// Create a migration for each 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.
|
/// 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
|
/// 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
|
/// 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>
|
/// <param name="options"></param>
|
||||||
public UmbracoDbContext(DbContextOptions<UmbracoDbContext> options)
|
public UmbracoDbContext(DbContextOptions<UmbracoDbContext> options)
|
||||||
: base(ConfigureOptions(options))
|
: base(ConfigureOptions(options))
|
||||||
{
|
{ }
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DbContextOptions<UmbracoDbContext> ConfigureOptions(DbContextOptions<UmbracoDbContext> options)
|
private static DbContextOptions<UmbracoDbContext> ConfigureOptions(DbContextOptions<UmbracoDbContext> options)
|
||||||
{
|
{
|
||||||
IOptionsMonitor<ConnectionStrings> connectionStringsOptionsMonitor = StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<ConnectionStrings>>();
|
var extensions = options.Extensions.FirstOrDefault() as Microsoft.EntityFrameworkCore.Infrastructure.CoreOptionsExtension;
|
||||||
|
IServiceProvider? serviceProvider = extensions?.ApplicationServiceProvider;
|
||||||
ConnectionStrings connectionStrings = connectionStringsOptionsMonitor.CurrentValue;
|
serviceProvider ??= StaticServiceProvider.Instance;
|
||||||
|
if (serviceProvider == null)
|
||||||
if (string.IsNullOrWhiteSpace(connectionStrings.ConnectionString))
|
|
||||||
{
|
{
|
||||||
ILogger<UmbracoDbContext> logger = StaticServiceProvider.Instance.GetRequiredService<ILogger<UmbracoDbContext>>();
|
// If the service provider is null, we cannot resolve the connection string or migration provider.
|
||||||
logger.LogCritical("No connection string was found, cannot setup Umbraco EF Core context");
|
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
|
// 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.
|
// 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");
|
throw new InvalidOperationException("No connection string was found, cannot setup Umbraco EF Core context");
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<IMigrationProviderSetup> migrationProviders = StaticServiceProvider.Instance.GetServices<IMigrationProviderSetup>();
|
IEnumerable<IMigrationProviderSetup>? migrationProviders = serviceProvider?.GetServices<IMigrationProviderSetup>();
|
||||||
IMigrationProviderSetup? migrationProvider = migrationProviders.FirstOrDefault(x => x.ProviderName.CompareProviderNames(connectionStrings.ProviderName));
|
IMigrationProviderSetup? migrationProvider = migrationProviders?.FirstOrDefault(x => x.ProviderName.CompareProviderNames(connectionStrings.ProviderName));
|
||||||
|
|
||||||
if (migrationProvider == null && connectionStrings.ProviderName != null)
|
if (migrationProvider == null && connectionStrings.ProviderName != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ internal sealed class CustomDbContextUmbracoProviderTests : UmbracoIntegrationTe
|
|||||||
|
|
||||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options) =>
|
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options, connectionString, providerName) =>
|
||||||
{
|
{
|
||||||
options.UseUmbracoDatabaseProvider(serviceProvider);
|
options.UseUmbracoDatabaseProvider(serviceProvider);
|
||||||
});
|
});
|
||||||
@@ -53,7 +53,7 @@ public class CustomDbContextCustomSqliteProviderTests : UmbracoIntegrationTest
|
|||||||
|
|
||||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
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;");
|
options.UseSqlite("Data Source=:memory:;Version=3;New=True;");
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user