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

Use consistent lifetime and registration pattern for design-time services #24519

Merged
merged 2 commits into from
Apr 15, 2021
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
32 changes: 32 additions & 0 deletions src/EFCore.Cosmos/Design/Internal/CosmosDesignTimeServices.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.DependencyInjection;

[assembly: DesignTimeProviderServices("Microsoft.EntityFrameworkCore.Cosmos.Design.Internal.CosmosDesignTimeServices")]

namespace Microsoft.EntityFrameworkCore.Cosmos.Design.Internal
{
/// <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 class CosmosDesignTimeServices : IDesignTimeServices
{
/// <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 void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
{
serviceCollection.AddEntityFrameworkCosmos();
new EntityFrameworkDesignServicesBuilder(serviceCollection)
.TryAddCoreServices();
}
}
}
115 changes: 46 additions & 69 deletions src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,17 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Design;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.EntityFrameworkCore.Scaffolding.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -47,58 +41,37 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices(
reporter = new OperationReporter(handler: null);
}

new EntityFrameworkRelationalDesignServicesBuilder(services)
.TryAddProviderSpecificServices(services => services
.TryAddSingleton<CSharpMigrationOperationGeneratorDependencies, CSharpMigrationOperationGeneratorDependencies>()
.TryAddSingleton<CSharpMigrationsGeneratorDependencies, CSharpMigrationsGeneratorDependencies>()
.TryAddSingleton<CSharpSnapshotGeneratorDependencies, CSharpSnapshotGeneratorDependencies>()
.TryAddSingleton<ICandidateNamingService, CandidateNamingService>()
.TryAddSingleton<ICSharpDbContextGenerator, CSharpDbContextGenerator>()
.TryAddSingleton<ICSharpEntityTypeGenerator, CSharpEntityTypeGenerator>()
.TryAddSingleton<ICSharpHelper, CSharpHelper>()
.TryAddSingleton<ICSharpMigrationOperationGenerator, CSharpMigrationOperationGenerator>()
.TryAddSingleton<ICSharpSnapshotGenerator, CSharpSnapshotGenerator>()
.TryAddSingleton<ICSharpUtilities, CSharpUtilities>()
.TryAddSingleton(reporter)
.TryAddSingleton<IMigrationsCodeGenerator, CSharpMigrationsGenerator>()
.TryAddSingleton<IMigrationsCodeGeneratorSelector, MigrationsCodeGeneratorSelector>()
.TryAddSingleton<IModelCodeGenerator, CSharpModelGenerator>()
.TryAddSingleton<IModelCodeGeneratorSelector, ModelCodeGeneratorSelector>()
.TryAddSingleton<INamedConnectionStringResolver>(
new DesignTimeConnectionStringResolver(applicationServiceProviderAccessor))
.TryAddSingleton<IPluralizer, HumanizerPluralizer>()
.TryAddSingleton<IReverseEngineerScaffolder, ReverseEngineerScaffolder>()
.TryAddSingleton<IScaffoldingModelFactory, RelationalScaffoldingModelFactory>()
.TryAddSingleton<IScaffoldingTypeMapper, ScaffoldingTypeMapper>()
.TryAddSingleton<MigrationsCodeGeneratorDependencies, MigrationsCodeGeneratorDependencies>()
.TryAddSingleton<ModelCodeGeneratorDependencies, ModelCodeGeneratorDependencies>()
.TryAddScoped<MigrationsScaffolderDependencies, MigrationsScaffolderDependencies>()
.TryAddScoped<IMigrationsScaffolder, MigrationsScaffolder>()
.TryAddScoped<ISnapshotModelProcessor, SnapshotModelProcessor>());

return services
.AddSingleton<AnnotationCodeGeneratorDependencies>()
.AddSingleton<CSharpMigrationOperationGeneratorDependencies>()
.AddSingleton<CSharpMigrationsGeneratorDependencies>()
.AddSingleton<CSharpSnapshotGeneratorDependencies>()
.AddSingleton<DiagnosticSource>(new DiagnosticListener(DbLoggerCategory.Name))
.AddSingleton<IAnnotationCodeGenerator, AnnotationCodeGenerator>()
.AddSingleton<ICandidateNamingService, CandidateNamingService>()
.AddSingleton<IConstructorBindingFactory, ConstructorBindingFactory>()
.AddSingleton<ICSharpDbContextGenerator, CSharpDbContextGenerator>()
.AddSingleton<ICSharpEntityTypeGenerator, CSharpEntityTypeGenerator>()
.AddSingleton<ICSharpHelper, CSharpHelper>()
.AddSingleton<ICSharpMigrationOperationGenerator, CSharpMigrationOperationGenerator>()
.AddSingleton<ICSharpSnapshotGenerator, CSharpSnapshotGenerator>()
.AddSingleton<ICSharpUtilities, CSharpUtilities>()
.AddSingleton<IDbContextLogger, NullDbContextLogger>()
.AddSingleton(typeof(IDiagnosticsLogger<>), typeof(DiagnosticsLogger<>))
.AddSingleton<IInterceptors, Interceptors>()
.AddSingleton<ILoggingOptions, LoggingOptions>()
.AddSingleton<IMemberClassifier, MemberClassifier>()
.AddSingleton<IMigrationsCodeGenerator, CSharpMigrationsGenerator>()
.AddSingleton<IMigrationsCodeGeneratorSelector, MigrationsCodeGeneratorSelector>()
.AddSingleton<IModelCodeGenerator, CSharpModelGenerator>()
.AddSingleton<IModelCodeGeneratorSelector, ModelCodeGeneratorSelector>()
.AddSingleton<IModelRuntimeInitializer, ModelRuntimeInitializer>()
.AddSingleton<IModelValidator, RelationalModelValidator>()
.AddSingleton<INamedConnectionStringResolver>(
new DesignTimeConnectionStringResolver(applicationServiceProviderAccessor))
.AddSingleton(reporter)
.AddSingleton<IParameterBindingFactories, ParameterBindingFactories>()
.AddSingleton<IPluralizer, HumanizerPluralizer>()
.AddSingleton<IPropertyParameterBindingFactory, PropertyParameterBindingFactory>()
.AddSingleton<IRegisteredServices>(new RegisteredServices(services.Select(s => s.ServiceType)))
.AddSingleton<IReverseEngineerScaffolder, ReverseEngineerScaffolder>()
.AddSingleton<IScaffoldingModelFactory, RelationalScaffoldingModelFactory>()
.AddSingleton<IScaffoldingTypeMapper, ScaffoldingTypeMapper>()
.AddSingleton<ITypeMappingSource>(p => p.GetRequiredService<IRelationalTypeMappingSource>())
.AddSingleton<IValueConverterSelector, ValueConverterSelector>()
.AddSingleton<MigrationsCodeGeneratorDependencies>()
.AddSingleton<ModelCodeGeneratorDependencies>()
.AddSingleton<ModelRuntimeInitializerDependencies>()
.AddSingleton<ModelValidatorDependencies>()
.AddSingleton<ProviderCodeGeneratorDependencies>()
.AddSingleton<RelationalModelValidatorDependencies>()
.AddSingleton<RelationalTypeMappingSourceDependencies>()
.AddSingleton<RuntimeModelDependencies>()
.AddSingleton<TypeMappingSourceDependencies>()
.AddSingleton<ValueConverterSelectorDependencies>()
.AddTransient<MigrationsScaffolderDependencies>()
.AddTransient<IMigrationsScaffolder, MigrationsScaffolder>()
.AddTransient<ISnapshotModelProcessor, SnapshotModelProcessor>()
.AddLogging(b => b.SetMinimumLevel(LogLevel.Debug).AddProvider(new OperationLoggerProvider(reporter)));
.AddLogging(b => b.SetMinimumLevel(LogLevel.Debug).AddProvider(new OperationLoggerProvider(reporter)));
}

/// <summary>
Expand All @@ -110,17 +83,21 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices(
public static IServiceCollection AddDbContextDesignTimeServices(
this IServiceCollection services,
DbContext context)
=> services
.AddTransient(_ => context.GetService<ICurrentDbContext>())
.AddTransient(_ => context.GetService<IDatabaseProvider>())
.AddTransient(_ => context.GetService<IDbContextOptions>())
.AddTransient(_ => context.GetService<IHistoryRepository>())
.AddTransient(_ => context.GetService<IMigrationsAssembly>())
.AddTransient(_ => context.GetService<IMigrationsIdGenerator>())
.AddTransient(_ => context.GetService<IMigrationsModelDiffer>())
.AddTransient(_ => context.GetService<IMigrator>())
.AddTransient(_ => context.GetService<IRelationalTypeMappingSource>())
.AddTransient(_ => context.GetService<IDesignTimeModel>().Model)
.AddTransient(_ => context.GetService<IModelRuntimeInitializer>());
{
new EntityFrameworkRelationalServicesBuilder(services)
.TryAdd(context.GetService<IDatabaseProvider>())
bricelam marked this conversation as resolved.
Show resolved Hide resolved
.TryAdd(_ => context.GetService<IMigrationsIdGenerator>())
.TryAdd(_ => context.GetService<IRelationalTypeMappingSource>())
.TryAdd(_ => context.GetService<IModelRuntimeInitializer>())
.TryAdd(_ => context.GetService<LoggingDefinitions>())
.TryAdd(_ => context.GetService<ICurrentDbContext>())
.TryAdd(_ => context.GetService<IDbContextOptions>())
.TryAdd(_ => context.GetService<IHistoryRepository>())
.TryAdd(_ => context.GetService<IMigrationsAssembly>())
.TryAdd(_ => context.GetService<IMigrationsModelDiffer>())
.TryAdd(_ => context.GetService<IMigrator>())
.TryAdd(_ => context.GetService<IDesignTimeModel>().Model);
return services;
}
}
}
16 changes: 9 additions & 7 deletions src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ public virtual IServiceProvider Build(DbContext context)
/// </summary>
public virtual IServiceCollection CreateServiceCollection(DbContext context)
{
var services = new ServiceCollection()
.AddEntityFrameworkDesignTimeServices(_reporter)
.AddDbContextDesignTimeServices(context);
var services = new ServiceCollection();
var provider = context.GetService<IDatabaseProvider>().Name;
ConfigureProviderServices(provider, services);

services.AddDbContextDesignTimeServices(context);
ConfigureReferencedServices(services, provider);
ConfigureProviderServices(provider, services);
services.AddEntityFrameworkDesignTimeServices(_reporter);
ConfigureUserServices(services);
return services;
}
Expand All @@ -87,10 +88,11 @@ public virtual IServiceProvider Build(string provider)
/// </summary>
public virtual IServiceCollection CreateServiceCollection(string provider)
{
var services = new ServiceCollection()
.AddEntityFrameworkDesignTimeServices(_reporter, GetApplicationServices);
ConfigureProviderServices(provider, services, throwOnError: true);
var services = new ServiceCollection();

ConfigureReferencedServices(services, provider);
ConfigureProviderServices(provider, services, throwOnError: true);
services.AddEntityFrameworkDesignTimeServices(_reporter, GetApplicationServices);
ConfigureUserServices(services);
return services;
}
Expand Down
10 changes: 5 additions & 5 deletions src/EFCore.Design/Design/Internal/MigrationsOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,13 @@ public virtual MigrationFiles AddMigration(
EnsureServices(services);
EnsureMigrationsAssembly(services);

var scaffolder = services.GetRequiredService<IMigrationsScaffolder>();
using var scope = services.CreateScope();
var scaffolder = scope.ServiceProvider.GetRequiredService<IMigrationsScaffolder>();
var migration =
string.IsNullOrEmpty(@namespace)
? scaffolder.ScaffoldMigration(name, _rootNamespace ?? string.Empty, subNamespace, _language)
: scaffolder.ScaffoldMigration(name, null, @namespace, _language);
var files = scaffolder.Save(_projectDir, migration, outputDir);

return files;
return scaffolder.Save(_projectDir, migration, outputDir);
}

// if outputDir is a subfolder of projectDir, then use each subfolder as a subnamespace
Expand Down Expand Up @@ -246,7 +245,8 @@ public virtual MigrationFiles RemoveMigration(
EnsureServices(services);
EnsureMigrationsAssembly(services);

var scaffolder = services.GetRequiredService<IMigrationsScaffolder>();
using var scope = services.CreateScope();
var scaffolder = scope.ServiceProvider.GetRequiredService<IMigrationsScaffolder>();

var files = scaffolder.RemoveMigration(_projectDir, _rootNamespace, force, _language);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.EntityFrameworkCore.Design
{
/// <summary>
/// <para>
/// A builder API designed for database providers to use when implementing <see cref="IDesignTimeServices" />.
/// </para>
/// <para>
/// Providers should create an instance of this class, use its methods to register
/// services, and then call <see cref="TryAddCoreServices" /> to fill out the remaining Entity
/// Framework services.
/// </para>
/// <para>
/// Entity Framework ensures that services are registered with the appropriate scope. In some cases a provider
/// may register a service with a different scope, but great care must be taken that all its dependencies
/// can handle the new scope, and that it does not cause issue for services that depend on it.
/// </para>
/// </summary>
public class EntityFrameworkRelationalDesignServicesBuilder : EntityFrameworkDesignServicesBuilder
{
/// <summary>
/// <para>
/// 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.
/// </para>
/// <para>
/// This dictionary is exposed for testing and provider-validation only.
/// It should not be used from application code.
/// </para>
/// </summary>
[EntityFrameworkInternal]
public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServices
= new Dictionary<Type, ServiceCharacteristics>
{
{ typeof(IAnnotationCodeGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IProviderConfigurationCodeGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IDatabaseModelFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }
};

/// <summary>
/// Creates a new <see cref="EntityFrameworkDesignServicesBuilder" /> for
/// registration of provider services.
/// </summary>
/// <param name="serviceCollection"> The collection to which services will be registered. </param>
public EntityFrameworkRelationalDesignServicesBuilder(IServiceCollection serviceCollection)
: base(serviceCollection)
{
}

/// <summary>
/// Gets the <see cref="ServiceCharacteristics" /> for the given service type.
/// </summary>
/// <param name="serviceType"> The type that defines the service API. </param>
/// <returns> The <see cref="ServiceCharacteristics" /> for the type. </returns>
/// <exception cref="InvalidOperationException"> when the type is not an EF service. </exception>
protected override ServiceCharacteristics GetServiceCharacteristics(Type serviceType)
=> RelationalServices.TryGetValue(serviceType, out var characteristics)
? characteristics
: base.GetServiceCharacteristics(serviceType);

/// <summary>
/// Registers default implementations of all services, including relational services, not already
/// registered by the provider. Relational database providers must call this method as the last
/// step of service registration--that is, after all provider services have been registered.
/// </summary>
/// <returns> This builder, such that further calls can be chained. </returns>
public override EntityFrameworkServicesBuilder TryAddCoreServices()
{
TryAdd<IAnnotationCodeGenerator, AnnotationCodeGenerator>();

ServiceCollectionMap.GetInfrastructure()
.AddDependencySingleton<AnnotationCodeGeneratorDependencies>()
.AddDependencySingleton<ProviderCodeGeneratorDependencies>();

return base.TryAddCoreServices();
}
}
}
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Microsoft.EntityFrameworkCore.Design
{
/// <summary>
/// Implemented by database providers to control which <see cref="IAnnotation" />s need to
/// Implemented by database providers to control which annotations need to
/// have code generated (as opposed to being handled by convention) and then to generate
/// the code if needed.
/// </summary>
Expand Down
Loading