Skip to content

Commit 4bf2fbf

Browse files
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 <[email protected]>
1 parent 7c9c733 commit 4bf2fbf

File tree

4 files changed

+78
-31
lines changed

4 files changed

+78
-31
lines changed

src/Umbraco.Cms.Persistence.EFCore/Composition/UmbracoEFCoreComposer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public void Compose(IUmbracoBuilder builder)
1919
builder.AddNotificationAsyncHandler<DatabaseSchemaAndDataCreatedNotification, EFCoreCreateTablesNotificationHandler>();
2020
builder.AddNotificationAsyncHandler<UnattendedInstallNotification, EFCoreCreateTablesNotificationHandler>();
2121

22-
builder.Services.AddUmbracoDbContext<UmbracoDbContext>((options) =>
22+
builder.Services.AddUmbracoDbContext<UmbracoDbContext>((provider, options, connectionString, providerName) =>
2323
{
2424
// Register the entity sets needed by OpenIddict.
2525
options.UseOpenIddict();

src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,50 @@ public static class UmbracoEFCoreServiceCollectionExtensions
1717
/// <summary>
1818
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
1919
/// </summary>
20-
/// <typeparam name="T"></typeparam>
21-
/// <param name="services"></param>
22-
/// <param name="optionsAction"></param>
23-
/// <returns></returns>
24-
public static IServiceCollection AddUmbracoDbContext<T>(this IServiceCollection services, Action<DbContextOptionsBuilder>? optionsAction = null)
20+
[Obsolete("Please use the method overload that takes all parameters for the optionsAction. Scheduled for removal in Umbraco 18.")]
21+
public static IServiceCollection AddUmbracoDbContext<T>(
22+
this IServiceCollection services,
23+
Action<DbContextOptionsBuilder>? optionsAction = null)
24+
where T : DbContext
25+
=> AddUmbracoDbContext<T>(services, (sp, optionsBuilder, connectionString, providerName) => optionsAction?.Invoke(optionsBuilder));
26+
27+
/// <summary>
28+
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
29+
/// </summary>
30+
public static IServiceCollection AddUmbracoDbContext<T>(
31+
this IServiceCollection services,
32+
Action<DbContextOptionsBuilder, string?, string?, IServiceProvider?>? optionsAction = null)
2533
where T : DbContext
2634
{
27-
return AddUmbracoDbContext<T>(services, (IServiceProvider _, DbContextOptionsBuilder options) =>
35+
return AddUmbracoDbContext<T>(services, (IServiceProvider provider, DbContextOptionsBuilder optionsBuilder, string? providerName, string? connectionString) =>
2836
{
29-
optionsAction?.Invoke(options);
37+
ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider);
38+
optionsAction?.Invoke(optionsBuilder, connectionStrings.ConnectionString, connectionStrings.ProviderName, provider);
3039
});
3140
}
3241

3342
/// <summary>
3443
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
3544
/// </summary>
36-
/// <typeparam name="T"></typeparam>
37-
/// <param name="services"></param>
38-
/// <param name="optionsAction"></param>
39-
/// <returns></returns>
40-
public static IServiceCollection AddUmbracoDbContext<T>(this IServiceCollection services, Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction = null)
45+
[Obsolete("Please use the method overload that takes all parameters for the optionsAction. Scheduled for removal in Umbraco 18.")]
46+
public static IServiceCollection AddUmbracoDbContext<T>(
47+
this IServiceCollection services,
48+
Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction = null)
49+
where T : DbContext
50+
=> AddUmbracoDbContext<T>(services, (sp, optionsBuilder, connectionString, providerName) => optionsAction?.Invoke(sp, optionsBuilder));
51+
52+
/// <summary>
53+
/// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes.
54+
/// </summary>
55+
public static IServiceCollection AddUmbracoDbContext<T>(
56+
this IServiceCollection services,
57+
Action<IServiceProvider, DbContextOptionsBuilder, string?, string?>? optionsAction = null)
4158
where T : DbContext
4259
{
43-
optionsAction ??= (sp, options) => { };
60+
optionsAction ??= (sp, optionsBuilder, connectionString, providerName) => { };
61+
4462

45-
services.AddPooledDbContextFactory<T>(optionsAction);
63+
services.AddPooledDbContextFactory<T>((provider, optionsBuilder) => SetupDbContext(optionsAction, provider, optionsBuilder));
4664
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
4765

4866
services.AddUnique<IAmbientEFCoreScopeStack<T>, AmbientEFCoreScopeStack<T>>();
@@ -110,4 +128,25 @@ public static void UseUmbracoDatabaseProvider(this DbContextOptionsBuilder build
110128

111129
builder.UseDatabaseProvider(connectionStrings.ProviderName, connectionStrings.ConnectionString);
112130
}
131+
132+
private static void SetupDbContext(Action<IServiceProvider, DbContextOptionsBuilder, string?, string?>? optionsAction, IServiceProvider provider, DbContextOptionsBuilder builder)
133+
{
134+
ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider);
135+
136+
optionsAction?.Invoke(provider, builder, connectionStrings.ConnectionString, connectionStrings.ProviderName);
137+
}
138+
139+
private static ConnectionStrings GetConnectionStringAndProviderName(IServiceProvider serviceProvider)
140+
{
141+
ConnectionStrings connectionStrings = serviceProvider.GetRequiredService<IOptionsMonitor<ConnectionStrings>>().CurrentValue;
142+
143+
// Replace data directory
144+
string? dataDirectory = AppDomain.CurrentDomain.GetData(Constants.System.DataDirectoryName)?.ToString();
145+
if (string.IsNullOrEmpty(dataDirectory) is false)
146+
{
147+
connectionStrings.ConnectionString = connectionStrings.ConnectionString?.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory);
148+
}
149+
150+
return connectionStrings;
151+
}
113152
}

src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Configuration;
2+
using System.Diagnostics;
23
using Microsoft.EntityFrameworkCore;
34
using Microsoft.EntityFrameworkCore.Metadata;
45
using Microsoft.Extensions.DependencyInjection;
@@ -17,14 +18,14 @@ namespace Umbraco.Cms.Persistence.EFCore;
1718
/// and insure the 'src/Umbraco.Web.UI/appsettings.json' have a connection string set with the right provider.
1819
///
1920
/// Create a migration for each provider.
20-
/// <code>dotnet ef migrations add %Name% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer -c UmbracoDbContext -- --provider SqlServer</code>
21+
/// <code>dotnet ef migrations add %Name% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer -c UmbracoDbContext</code>
2122
///
22-
/// <code>dotnet ef migrations add %Name% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite -c UmbracoDbContext -- --provider Sqlite</code>
23+
/// <code>dotnet ef migrations add %Name% -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite -c UmbracoDbContext</code>
2324
///
2425
/// Remove the last migration for each provider.
25-
/// <code>dotnet ef migrations remove -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer -- --provider SqlServer</code>
26+
/// <code>dotnet ef migrations remove -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.SqlServer</code>
2627
///
27-
/// <code>dotnet ef migrations remove -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite -- --provider Sqlite</code>
28+
/// <code>dotnet ef migrations remove -s src/Umbraco.Web.UI -p src/Umbraco.Cms.Persistence.EFCore.Sqlite</code>
2829
///
2930
/// To find documentation about this way of working with the context see
3031
/// 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
3738
/// <param name="options"></param>
3839
public UmbracoDbContext(DbContextOptions<UmbracoDbContext> options)
3940
: base(ConfigureOptions(options))
40-
{
41-
42-
}
41+
{ }
4342

4443
private static DbContextOptions<UmbracoDbContext> ConfigureOptions(DbContextOptions<UmbracoDbContext> options)
4544
{
46-
IOptionsMonitor<ConnectionStrings> connectionStringsOptionsMonitor = StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<ConnectionStrings>>();
45+
var extensions = options.Extensions.FirstOrDefault() as Microsoft.EntityFrameworkCore.Infrastructure.CoreOptionsExtension;
46+
IServiceProvider? serviceProvider = extensions?.ApplicationServiceProvider;
47+
serviceProvider ??= StaticServiceProvider.Instance;
48+
if (serviceProvider == null)
49+
{
50+
// If the service provider is null, we cannot resolve the connection string or migration provider.
51+
throw new InvalidOperationException("The service provider is not configured. Ensure that UmbracoDbContext is registered correctly.");
52+
}
53+
54+
IOptionsMonitor<ConnectionStrings>? connectionStringsOptionsMonitor = serviceProvider?.GetRequiredService<IOptionsMonitor<ConnectionStrings>>();
4755

48-
ConnectionStrings connectionStrings = connectionStringsOptionsMonitor.CurrentValue;
56+
ConnectionStrings? connectionStrings = connectionStringsOptionsMonitor?.CurrentValue;
4957

50-
if (string.IsNullOrWhiteSpace(connectionStrings.ConnectionString))
58+
if (string.IsNullOrWhiteSpace(connectionStrings?.ConnectionString))
5159
{
52-
ILogger<UmbracoDbContext> logger = StaticServiceProvider.Instance.GetRequiredService<ILogger<UmbracoDbContext>>();
53-
logger.LogCritical("No connection string was found, cannot setup Umbraco EF Core context");
60+
ILogger<UmbracoDbContext>? logger = serviceProvider?.GetRequiredService<ILogger<UmbracoDbContext>>();
61+
logger?.LogCritical("No connection string was found, cannot setup Umbraco EF Core context");
5462

5563
// we're throwing an exception here to make it abundantly clear that one should never utilize (or have a
5664
// dependency on) the DbContext before the connection string has been initialized by the installer.
5765
throw new InvalidOperationException("No connection string was found, cannot setup Umbraco EF Core context");
5866
}
5967

60-
IEnumerable<IMigrationProviderSetup> migrationProviders = StaticServiceProvider.Instance.GetServices<IMigrationProviderSetup>();
61-
IMigrationProviderSetup? migrationProvider = migrationProviders.FirstOrDefault(x => x.ProviderName.CompareProviderNames(connectionStrings.ProviderName));
68+
IEnumerable<IMigrationProviderSetup>? migrationProviders = serviceProvider?.GetServices<IMigrationProviderSetup>();
69+
IMigrationProviderSetup? migrationProvider = migrationProviders?.FirstOrDefault(x => x.ProviderName.CompareProviderNames(connectionStrings.ProviderName));
6270

6371
if (migrationProvider == null && connectionStrings.ProviderName != null)
6472
{

tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextUmbracoProviderTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void Can_Register_Custom_DbContext_And_Resolve()
2222

2323
protected override void CustomTestSetup(IUmbracoBuilder builder)
2424
{
25-
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options) =>
25+
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options, connectionString, providerName) =>
2626
{
2727
options.UseUmbracoDatabaseProvider(serviceProvider);
2828
});
@@ -53,7 +53,7 @@ public void Can_Register_Custom_DbContext_And_Resolve()
5353

5454
protected override void CustomTestSetup(IUmbracoBuilder builder)
5555
{
56-
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options) =>
56+
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options, connectionString, providerName) =>
5757
{
5858
options.UseSqlite("Data Source=:memory:;Version=3;New=True;");
5959
});

0 commit comments

Comments
 (0)