Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support resolving application services from singleton internal services #29950

Merged
merged 2 commits into from
Dec 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/EFCore/DbContextOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,41 @@ public virtual DbContextOptionsBuilder UseInternalServiceProvider(IServiceProvid
public virtual DbContextOptionsBuilder UseApplicationServiceProvider(IServiceProvider? serviceProvider)
=> WithOption(e => e.WithApplicationServiceProvider(serviceProvider));

/// <summary>
/// Sets the root <see cref="IServiceProvider" /> from which singleton application services can be obtained from singleton
/// internal services.
/// </summary>
/// <remarks>
/// <para>
/// This is an advanced option that is rarely needed by normal applications. Calling this method will result in a new internal
/// service provider being created for every different root application service provider.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-dbcontext-options">Using DbContextOptions</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="rootServiceProvider">The service provider to be used.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public virtual DbContextOptionsBuilder UseRootApplicationServiceProvider(IServiceProvider? rootServiceProvider)
=> WithOption(e => e.WithRootApplicationServiceProvider(rootServiceProvider));

/// <summary>
/// Resolves the root <see cref="IServiceProvider" /> from from the scoped application service provider. The root provider can
/// be used to obtain singleton application services from singleton internal services.
/// </summary>
/// <remarks>
/// <para>
/// This is an advanced option that is rarely needed by normal applications. Calling this method will result in a new internal
/// service provider being created for every different root application service provider.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-dbcontext-options">Using DbContextOptions</see> for more information and examples.
/// </para>
/// </remarks>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public virtual DbContextOptionsBuilder ResolveRootApplicationServiceProvider()
=> WithOption(e => e.WithAutoResolveRootApplicationServiceProvider(true));

/// <summary>
/// Enables application data to be included in exception messages, logging, etc. This can include the
/// values assigned to properties of your entity instances, parameter values for commands being sent
Expand Down
35 changes: 35 additions & 0 deletions src/EFCore/DbContextOptionsBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,41 @@ public DbContextOptionsBuilder(DbContextOptions<TContext> options)
public new virtual DbContextOptionsBuilder<TContext> UseApplicationServiceProvider(IServiceProvider? serviceProvider)
=> (DbContextOptionsBuilder<TContext>)base.UseApplicationServiceProvider(serviceProvider);

/// <summary>
/// Sets the root <see cref="IServiceProvider" /> from which singleton application services can be obtained from singleton
/// internal services.
/// </summary>
/// <remarks>
/// <para>
/// This is an advanced option that is rarely needed by normal applications. Calling this method will result in a new internal
/// service provider being created for every different root application service provider.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-dbcontext-options">Using DbContextOptions</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="rootServiceProvider">The service provider to be used.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public new virtual DbContextOptionsBuilder<TContext> UseRootApplicationServiceProvider(IServiceProvider? rootServiceProvider)
=> (DbContextOptionsBuilder<TContext>)base.UseRootApplicationServiceProvider(rootServiceProvider);

/// <summary>
/// Resolves the root <see cref="IServiceProvider" /> from from the scoped application service provider. The root provider can
/// be used to obtain singleton application services from singleton internal services.
/// </summary>
/// <remarks>
/// <para>
/// This is an advanced option that is rarely needed by normal applications. Calling this method will result in a new internal
/// service provider being created for every different root application service provider.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-dbcontext-options">Using DbContextOptions</see> for more information and examples.
/// </para>
/// </remarks>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public new virtual DbContextOptionsBuilder<TContext> ResolveRootApplicationServiceProvider()
=> (DbContextOptionsBuilder<TContext>)base.ResolveRootApplicationServiceProvider();

/// <summary>
/// Enables application data to be included in exception messages, logging, etc. This can include the
/// values assigned to properties of your entity instances, parameter values for commands being sent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,8 @@ private static void AddCoreServices<TContextImplementation>(
ServiceLifetime optionsLifetime)
where TContextImplementation : DbContext
{
serviceCollection.TryAddSingleton<ServiceProviderAccessor>();

serviceCollection.TryAdd(
new ServiceDescriptor(
typeof(DbContextOptions<TContextImplementation>),
Expand Down
54 changes: 54 additions & 0 deletions src/EFCore/Infrastructure/CoreOptionsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public class CoreOptionsExtension : IDbContextOptionsExtension
{
private IServiceProvider? _internalServiceProvider;
private IServiceProvider? _applicationServiceProvider;
private IServiceProvider? _rootApplicationServiceProvider;
private bool _autoResolveResolveRootProvider;
private IModel? _model;
private ILoggerFactory? _loggerFactory;
private IDbContextLogger? _contextLogger;
Expand Down Expand Up @@ -66,6 +68,8 @@ protected CoreOptionsExtension(CoreOptionsExtension copyFrom)
{
_internalServiceProvider = copyFrom.InternalServiceProvider;
_applicationServiceProvider = copyFrom.ApplicationServiceProvider;
_rootApplicationServiceProvider = copyFrom.RootApplicationServiceProvider;
_autoResolveResolveRootProvider = copyFrom.AutoResolveRootProvider;
_model = copyFrom.Model;
_loggerFactory = copyFrom.LoggerFactory;
_contextLogger = copyFrom.DbContextLogger;
Expand Down Expand Up @@ -126,6 +130,42 @@ public virtual CoreOptionsExtension WithApplicationServiceProvider(IServiceProvi
var clone = Clone();

clone._applicationServiceProvider = applicationServiceProvider;
clone._rootApplicationServiceProvider ??= _autoResolveResolveRootProvider
? applicationServiceProvider?.GetService<ServiceProviderAccessor>()?.RootServiceProvider
: null;

return clone;
}

/// <summary>
/// Creates a new instance with all options the same as for this instance, but with the given option changed.
/// It is unusual to call this method directly. Instead use <see cref="DbContextOptionsBuilder" />.
/// </summary>
/// <param name="rootApplicationServiceProvider">The option to change.</param>
/// <returns>A new instance with the option changed.</returns>
public virtual CoreOptionsExtension WithRootApplicationServiceProvider(IServiceProvider? rootApplicationServiceProvider)
{
var clone = Clone();

clone._rootApplicationServiceProvider = rootApplicationServiceProvider;

return clone;
}

/// <summary>
/// Creates a new instance with all options the same as for this instance, but with the given option changed.
/// It is unusual to call this method directly. Instead use <see cref="DbContextOptionsBuilder" />.
/// </summary>
/// <param name="autoResolve">The option to change.</param>
/// <returns>A new instance with the option changed.</returns>
public virtual CoreOptionsExtension WithAutoResolveRootApplicationServiceProvider(bool autoResolve = true)
{
var clone = Clone();

clone._autoResolveResolveRootProvider = autoResolve;
clone._rootApplicationServiceProvider ??= autoResolve
? _applicationServiceProvider?.GetService<ServiceProviderAccessor>()?.RootServiceProvider
: null;

return clone;
}
Expand Down Expand Up @@ -420,6 +460,18 @@ public virtual IServiceProvider? InternalServiceProvider
public virtual IServiceProvider? ApplicationServiceProvider
=> _applicationServiceProvider;

/// <summary>
/// The option set from the <see cref="DbContextOptionsBuilder.UseRootApplicationServiceProvider" /> method.
/// </summary>
public virtual IServiceProvider? RootApplicationServiceProvider
=> _rootApplicationServiceProvider;

/// <summary>
/// The option set from the <see cref="DbContextOptionsBuilder.UseRootApplicationServiceProvider" /> method.
/// </summary>
public virtual bool AutoResolveRootProvider
=> _autoResolveResolveRootProvider;

/// <summary>
/// The options set from the <see cref="DbContextOptionsBuilder.ConfigureWarnings" /> method.
/// </summary>
Expand Down Expand Up @@ -647,6 +699,7 @@ public override int GetServiceProviderHashCode()
hashCode.Add(Extension.GetMemoryCache());
hashCode.Add(Extension._sensitiveDataLoggingEnabled);
hashCode.Add(Extension._detailedErrorsEnabled);
hashCode.Add(Extension.RootApplicationServiceProvider);
hashCode.Add(Extension._threadSafetyChecksEnabled);
hashCode.Add(Extension._warningsConfiguration.GetServiceProviderHashCode());

Expand Down Expand Up @@ -677,6 +730,7 @@ public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo
&& Extension.GetMemoryCache() == otherInfo.Extension.GetMemoryCache()
&& Extension._sensitiveDataLoggingEnabled == otherInfo.Extension._sensitiveDataLoggingEnabled
&& Extension._detailedErrorsEnabled == otherInfo.Extension._detailedErrorsEnabled
&& Extension.RootApplicationServiceProvider == otherInfo.Extension.RootApplicationServiceProvider
&& Extension._threadSafetyChecksEnabled == otherInfo.Extension._threadSafetyChecksEnabled
&& Extension._warningsConfiguration.ShouldUseSameServiceProvider(otherInfo.Extension._warningsConfiguration)
&& (Extension._replacedServices == otherInfo.Extension._replacedServices
Expand Down
5 changes: 5 additions & 0 deletions src/EFCore/Infrastructure/ICoreSingletonOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ public interface ICoreSingletonOptions : ISingletonOptions
/// Reflects the option set by <see cref="DbContextOptionsBuilder.EnableThreadSafetyChecks" />.
/// </summary>
bool AreThreadSafetyChecksEnabled { get; }

/// <summary>
/// The root service provider for the application, if available. />.
/// </summary>
IServiceProvider? RootApplicationServiceProvider { get; }
}
17 changes: 17 additions & 0 deletions src/EFCore/Infrastructure/Internal/CoreSingletonOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public virtual void Initialize(IDbContextOptions options)

AreDetailedErrorsEnabled = coreOptions.DetailedErrorsEnabled;
AreThreadSafetyChecksEnabled = coreOptions.ThreadSafetyChecksEnabled;
RootApplicationServiceProvider = coreOptions.RootApplicationServiceProvider;
}

/// <summary>
Expand Down Expand Up @@ -54,6 +55,14 @@ public virtual void Validate(IDbContextOptions options)
nameof(DbContextOptionsBuilder.EnableThreadSafetyChecks),
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
}

if (RootApplicationServiceProvider != coreOptions.RootApplicationServiceProvider)
{
throw new InvalidOperationException(
CoreStrings.SingletonOptionChanged(
nameof(DbContextOptionsBuilder.UseRootApplicationServiceProvider),
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
}
}

/// <summary>
Expand All @@ -71,4 +80,12 @@ public virtual void Validate(IDbContextOptions options)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool AreThreadSafetyChecksEnabled { get; private set; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IServiceProvider? RootApplicationServiceProvider { get; private set; }
}
25 changes: 25 additions & 0 deletions src/EFCore/Infrastructure/ServiceProviderAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Infrastructure;

/// <summary>
/// This type is added as a singleton service to the application service provider to provide access to the
/// root service provider.
/// </summary>
public class ServiceProviderAccessor
{
/// <summary>
/// Initializes a new instance of the <see cref="ServiceProviderAccessor" /> class.
/// </summary>
/// <param name="rootServiceProvider">The injected service provider.</param>
public ServiceProviderAccessor(IServiceProvider rootServiceProvider)
{
RootServiceProvider = rootServiceProvider;
}

/// <summary>
/// The injected service provider.
/// </summary>
public virtual IServiceProvider RootServiceProvider { get; }
}
Loading