Merge branch 'release/12.3.0' into v12/dev
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Persistence.EFCore.Migrations;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -10,7 +11,7 @@ public class SqlServerMigrationProvider : IMigrationProvider
|
||||
|
||||
public SqlServerMigrationProvider(IDbContextFactory<UmbracoDbContext> dbContextFactory) => _dbContextFactory = dbContextFactory;
|
||||
|
||||
public string ProviderName => "Microsoft.Data.SqlClient";
|
||||
public string ProviderName => Constants.ProviderNames.SQLServer;
|
||||
|
||||
public async Task MigrateAsync(EFCoreMigration migration)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Persistence.EFCore.Migrations;
|
||||
|
||||
namespace Umbraco.Cms.Persistence.EFCore.SqlServer;
|
||||
|
||||
public class SqlServerMigrationProviderSetup : IMigrationProviderSetup
|
||||
{
|
||||
public string ProviderName => "Microsoft.Data.SqlClient";
|
||||
public string ProviderName => Constants.ProviderNames.SQLServer;
|
||||
|
||||
public void Setup(DbContextOptionsBuilder builder, string? connectionString)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Persistence.EFCore.Migrations;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -11,7 +12,7 @@ public class SqliteMigrationProvider : IMigrationProvider
|
||||
public SqliteMigrationProvider(IDbContextFactory<UmbracoDbContext> dbContextFactory)
|
||||
=> _dbContextFactory = dbContextFactory;
|
||||
|
||||
public string ProviderName => "Microsoft.Data.Sqlite";
|
||||
public string ProviderName => Constants.ProviderNames.SQLLite;
|
||||
|
||||
public async Task MigrateAsync(EFCoreMigration migration)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Persistence.EFCore.Migrations;
|
||||
|
||||
namespace Umbraco.Cms.Persistence.EFCore.Sqlite;
|
||||
|
||||
public class SqliteMigrationProviderSetup : IMigrationProviderSetup
|
||||
{
|
||||
public string ProviderName => "Microsoft.Data.Sqlite";
|
||||
public string ProviderName => Constants.ProviderNames.SQLLite;
|
||||
|
||||
public void Setup(DbContextOptionsBuilder builder, string? connectionString)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ public class UmbracoEFCoreComposer : IComposer
|
||||
builder.AddNotificationAsyncHandler<DatabaseSchemaAndDataCreatedNotification, EFCoreCreateTablesNotificationHandler>();
|
||||
builder.AddNotificationAsyncHandler<UnattendedInstallNotification, EFCoreCreateTablesNotificationHandler>();
|
||||
|
||||
builder.Services.AddUmbracoEFCoreContext<UmbracoDbContext>((options, connectionString, providerName) =>
|
||||
builder.Services.AddUmbracoDbContext<UmbracoDbContext>((options) =>
|
||||
{
|
||||
// Register the entity sets needed by OpenIddict.
|
||||
options.UseOpenIddict();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DistributedLocking;
|
||||
@@ -16,6 +18,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
||||
{
|
||||
public delegate void DefaultEFCoreOptionsAction(DbContextOptionsBuilder options, string? providerName, string? connectionString);
|
||||
|
||||
[Obsolete("Use AddUmbracoDbContext<T>(this IServiceCollection services, Action<DbContextOptionsBuilder>? optionsAction = null) instead.")]
|
||||
public static IServiceCollection AddUmbracoEFCoreContext<T>(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null)
|
||||
where T : DbContext
|
||||
{
|
||||
@@ -24,7 +27,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
||||
sp =>
|
||||
{
|
||||
SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder);
|
||||
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(),optionsBuilder.Options);
|
||||
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(), optionsBuilder.Options);
|
||||
});
|
||||
services.AddPooledDbContextFactory<T>((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder));
|
||||
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
|
||||
@@ -38,6 +41,7 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
||||
return services;
|
||||
}
|
||||
|
||||
[Obsolete("Use AddUmbracoDbContext<T>(this IServiceCollection services, Action<DbContextOptionsBuilder>? optionsAction = null) instead.")]
|
||||
public static IServiceCollection AddUmbracoEFCoreContext<T>(this IServiceCollection services, string connectionString, string providerName, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null)
|
||||
where T : DbContext
|
||||
{
|
||||
@@ -52,8 +56,8 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
||||
services.TryAddSingleton<IDbContextFactory<T>>(
|
||||
sp =>
|
||||
{
|
||||
SetupDbContext(defaultEFCoreOptionsAction, sp, optionsBuilder);
|
||||
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(),optionsBuilder.Options);
|
||||
defaultEFCoreOptionsAction?.Invoke(optionsBuilder, providerName, connectionString);
|
||||
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(), optionsBuilder.Options);
|
||||
});
|
||||
services.AddPooledDbContextFactory<T>(options => defaultEFCoreOptionsAction?.Invoke(options, providerName, connectionString));
|
||||
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
|
||||
@@ -67,12 +71,117 @@ public static class UmbracoEFCoreServiceCollectionExtensions
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
where T : DbContext
|
||||
{
|
||||
return AddUmbracoDbContext<T>(services, (IServiceProvider _, DbContextOptionsBuilder options) =>
|
||||
{
|
||||
optionsAction?.Invoke(options);
|
||||
});
|
||||
}
|
||||
|
||||
/// <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)
|
||||
where T : DbContext
|
||||
{
|
||||
optionsAction ??= (sp, options) => { };
|
||||
|
||||
var optionsBuilder = new DbContextOptionsBuilder<T>();
|
||||
|
||||
services.TryAddSingleton<IDbContextFactory<T>>(sp =>
|
||||
{
|
||||
optionsAction.Invoke(sp, optionsBuilder);
|
||||
return new UmbracoPooledDbContextFactory<T>(sp.GetRequiredService<IRuntimeState>(), optionsBuilder.Options);
|
||||
});
|
||||
services.AddPooledDbContextFactory<T>(optionsAction);
|
||||
services.AddTransient(services => services.GetRequiredService<IDbContextFactory<T>>().CreateDbContext());
|
||||
|
||||
services.AddUnique<IAmbientEFCoreScopeStack<T>, AmbientEFCoreScopeStack<T>>();
|
||||
services.AddUnique<IEFCoreScopeAccessor<T>, EFCoreScopeAccessor<T>>();
|
||||
services.AddUnique<IEFCoreScopeProvider<T>, EFCoreScopeProvider<T>>();
|
||||
services.AddSingleton<IDistributedLockingMechanism, SqliteEFCoreDistributedLockingMechanism<T>>();
|
||||
services.AddSingleton<IDistributedLockingMechanism, SqlServerEFCoreDistributedLockingMechanism<T>>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the database provider. I.E UseSqlite or UseSqlServer based on the provider name.
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
/// <param name="providerName"></param>
|
||||
/// <param name="connectionString"></param>
|
||||
/// <exception cref="InvalidDataException"></exception>
|
||||
/// <remarks>
|
||||
/// Only supports the databases normally supported in Umbraco.
|
||||
/// </remarks>
|
||||
public static void UseDatabaseProvider(this DbContextOptionsBuilder builder, string providerName, string connectionString)
|
||||
{
|
||||
switch (providerName)
|
||||
{
|
||||
case Constants.ProviderNames.SQLServer:
|
||||
builder.UseSqlServer(connectionString);
|
||||
break;
|
||||
case Constants.ProviderNames.SQLLite:
|
||||
builder.UseSqlite(connectionString);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDataException($"The provider {providerName} is not supported. Manually add the add the UseXXX statement to the options. I.E UseNpgsql()");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the database provider to use based on the Umbraco connection string.
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
/// <param name="serviceProvider"></param>
|
||||
public static void UseUmbracoDatabaseProvider(this DbContextOptionsBuilder builder, 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);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(connectionStrings.ProviderName))
|
||||
{
|
||||
Log.Warning("No database provider was set. ProviderName is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(connectionStrings.ConnectionString))
|
||||
{
|
||||
Log.Warning("No database provider was set. Connection string is null");
|
||||
return;
|
||||
}
|
||||
|
||||
builder.UseDatabaseProvider(connectionStrings.ProviderName, connectionStrings.ConnectionString);
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
private static void SetupDbContext(DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction, IServiceProvider provider, DbContextOptionsBuilder builder)
|
||||
{
|
||||
ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider);
|
||||
defaultEFCoreOptionsAction?.Invoke(builder, connectionStrings.ConnectionString, connectionStrings.ProviderName);
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
private static ConnectionStrings GetConnectionStringAndProviderName(IServiceProvider serviceProvider)
|
||||
{
|
||||
ConnectionStrings connectionStrings = serviceProvider.GetRequiredService<IOptionsMonitor<ConnectionStrings>>().CurrentValue;
|
||||
|
||||
@@ -3,7 +3,6 @@ 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;
|
||||
@@ -77,7 +76,7 @@ public class UmbracoDbContext : DbContext
|
||||
|
||||
foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
entity.SetTableName(Constants.DatabaseSchema.TableNamePrefix + entity.GetTableName());
|
||||
entity.SetTableName(Core.Constants.DatabaseSchema.TableNamePrefix + entity.GetTableName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/Umbraco.Core/Constants-ProviderNames.cs
Normal file
11
src/Umbraco.Core/Constants-ProviderNames.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Cms.Core;
|
||||
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class ProviderNames
|
||||
{
|
||||
public const string SQLLite = "Microsoft.Data.Sqlite";
|
||||
|
||||
public const string SQLServer = "Microsoft.Data.SqlClient";
|
||||
}
|
||||
}
|
||||
@@ -35,15 +35,18 @@
|
||||
$scope.activeApp = null;
|
||||
|
||||
//initializes any watches
|
||||
function startWatches(content) {
|
||||
var watchers = [];
|
||||
|
||||
$scope.$watchGroup(['culture', 'segment'],
|
||||
function startWatches(content) {
|
||||
clearWatchers();
|
||||
|
||||
watchers.push($scope.$watchGroup(['culture', 'segment'],
|
||||
function (value, oldValue) {
|
||||
createPreviewButton($scope.content, value[0], value[1]);
|
||||
});
|
||||
}));
|
||||
|
||||
//watch for changes to isNew, set the page.isNew accordingly and load the breadcrumb if we can
|
||||
$scope.$watch('isNew', function (newVal, oldVal) {
|
||||
watchers.push($scope.$watch('isNew', function (newVal, oldVal) {
|
||||
|
||||
$scope.page.isNew = Object.toBoolean(newVal);
|
||||
|
||||
@@ -59,8 +62,12 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
function clearWatchers () {
|
||||
watchers.forEach(w => w());
|
||||
watchers = [];
|
||||
}
|
||||
|
||||
//this initializes the editor with the data which will be called more than once if the data is re-loaded
|
||||
@@ -109,6 +116,7 @@
|
||||
bindEvents();
|
||||
|
||||
resetVariantFlags();
|
||||
startWatches($scope.content);
|
||||
}
|
||||
|
||||
function loadBreadcrumb() {
|
||||
@@ -241,7 +249,6 @@
|
||||
|
||||
appendRuntimeData();
|
||||
init();
|
||||
startWatches($scope.content);
|
||||
|
||||
syncTreeNode($scope.content, $scope.content.path, true);
|
||||
|
||||
@@ -265,7 +272,6 @@
|
||||
|
||||
appendRuntimeData();
|
||||
init();
|
||||
startWatches($scope.content);
|
||||
|
||||
resetLastListPageNumber($scope.content);
|
||||
|
||||
@@ -346,7 +352,10 @@
|
||||
labelKey: "buttons_saveAndPreview"
|
||||
};
|
||||
|
||||
const activeVariant = content.variants?.find((variant) => content.documentType?.variations === "Nothing" || variant.compositeId === compositeId);
|
||||
let activeVariant = content.variants?.find((variant) => content.documentType?.variations === "Nothing" || variant.compositeId === compositeId);
|
||||
/* if we can't find the active variant and there is only one variant available, we will use that.
|
||||
this happens if we have a node that can vary by culture but there is only one language available. */
|
||||
activeVariant = !activeVariant && content.variants.length === 1 ? content.variants[0] : activeVariant;
|
||||
|
||||
$scope.previewSubButtons = activeVariant?.additionalPreviewUrls?.map((additionalPreviewUrl) => {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Persistence.EFCore.DbContext;
|
||||
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
|
||||
public class CustomDbContextUmbracoProviderTests : UmbracoIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public void Can_Register_Custom_DbContext_And_Resolve()
|
||||
{
|
||||
var dbContext = Services.GetRequiredService<CustomDbContext>();
|
||||
|
||||
Assert.IsNotNull(dbContext);
|
||||
Assert.IsNotEmpty(dbContext.Database.GetConnectionString());
|
||||
}
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options) =>
|
||||
{
|
||||
options.UseUmbracoDatabaseProvider(serviceProvider);
|
||||
});
|
||||
}
|
||||
|
||||
internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext
|
||||
{
|
||||
public CustomDbContext(DbContextOptions<CustomDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
|
||||
public class CustomDbContextCustomSqliteProviderTests : UmbracoIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public void Can_Register_Custom_DbContext_And_Resolve()
|
||||
{
|
||||
var dbContext = Services.GetRequiredService<CustomDbContext>();
|
||||
|
||||
Assert.IsNotNull(dbContext);
|
||||
Assert.IsNotEmpty(dbContext.Database.GetConnectionString());
|
||||
}
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddUmbracoDbContext<CustomDbContext>((serviceProvider, options) =>
|
||||
{
|
||||
options.UseSqlite("Data Source=:memory:;Version=3;New=True;");
|
||||
});
|
||||
}
|
||||
|
||||
internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext
|
||||
{
|
||||
public CustomDbContext(DbContextOptions<CustomDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
|
||||
public class CustomDbContextLegacyExtensionProviderTests : UmbracoIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public void Can_Register_Custom_DbContext_And_Resolve()
|
||||
{
|
||||
var dbContext = Services.GetRequiredService<CustomDbContext>();
|
||||
|
||||
Assert.IsNotNull(dbContext);
|
||||
Assert.IsNotEmpty(dbContext.Database.GetConnectionString());
|
||||
}
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddUmbracoEFCoreContext<CustomDbContext>("Data Source=:memory:;Version=3;New=True;", "Microsoft.Data.Sqlite", (options, connectionString, providerName) =>
|
||||
{
|
||||
options.UseSqlite(connectionString);
|
||||
});
|
||||
}
|
||||
|
||||
internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext
|
||||
{
|
||||
public CustomDbContext(DbContextOptions<CustomDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
|
||||
"version": "12.3.0-rc",
|
||||
"version": "12.3.0",
|
||||
"assemblyVersion": {
|
||||
"precision": "build"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user