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

EF cannot resolve custom IMigrationsSqlGenerator #21617

Closed
Rudy102Michal opened this issue Jul 14, 2020 · 2 comments
Closed

EF cannot resolve custom IMigrationsSqlGenerator #21617

Rudy102Michal opened this issue Jul 14, 2020 · 2 comments
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported

Comments

@Rudy102Michal
Copy link

Rudy102Michal commented Jul 14, 2020

EfSchemaPerTenantTest.zip

In order to implement schema-based multi-tenancy in my application (.Net Core REST Api), I wanted to derive from and override SqlServerMigrationsSqlGenerator. I've followed this tutorial from official docs and this thread from StackOverflow.

Basically, what I'd like to achieve, is to be able to generate a unique schema for every tenant (that my app would serve) based on migrations. And to be able to do this on a runtime.

I've prepared SchemaMigrationsSqlGenerator class that inherits from SqlServerMigrationsSqlGenerator:

public sealed class SchemaMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
{
    private readonly ISchemaNameProvider _schemaNameProvider;

    public SchemaMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, 
        IMigrationsAnnotationProvider migrationsAnnotations,
        ISchemaNameProvider schemaNameProvider) : base(dependencies, migrationsAnnotations)
    {
        _schemaNameProvider = schemaNameProvider;
    }

    protected override void Generate(MigrationOperation operation, IModel model, MigrationCommandListBuilder builder)
    {
        switch (operation)
        {
            case SqlServerCreateDatabaseOperation _:
                break;
            case EnsureSchemaOperation ensureSchemaOperation:
                ensureSchemaOperation.Name = _schemaNameProvider.SchemaName;
                break;
            case CreateTableOperation createTableOperation:
                createTableOperation.Schema = _schemaNameProvider.SchemaName;
                break;
            case CreateIndexOperation createIndexOperation:
                createIndexOperation.Schema = _schemaNameProvider.SchemaName;
                break;
            default:
                throw new NotImplementedException(
                    $"Migration operation of type {operation.GetType().Name} is not supported by SchemaMigrationsSqlGenerator.");
        }

        base.Generate(operation, model, builder);
    }
}

The ISchemaNameProvider is a scoped service registered in IServiceCollection:

public interface ISchemaNameProvider
{
    string SchemaName { get; }
}

public class SchemaNameProvider : ISchemaNameProvider
{
    private readonly IHttpContextAccessor _httpAccessor;
    private const string DefaultSchemaName = "default";

    public string SchemaName
    {
        get
        {
            var ctx = _httpAccessor.HttpContext;
            var query = ctx.Request.Query;

            if(!query.ContainsKey("tenant"))
                throw new SystemException("Http request is missing 'tenant' parameter in query.");

            return query["tenant"].ToString();
        }
    }

    public SchemaNameProvider(IHttpContextAccessor accessor)
    {
        _httpAccessor = accessor;
    }
}

public class DummySchemaNameProvider : ISchemaNameProvider
{
    public string SchemaName => "SCHEMA_PLACEHOLDER";
}

Services registration:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppDataContext>(opt =>
        {
            opt.UseSqlServer(Configuration["SQLDatabaseConnectionString"]);
            //opt.ReplaceService<IMigrationsSqlGenerator, SchemaMigrationsSqlGenerator>();
        });
    services.AddScoped<ISchemaNameProvider>(ctx =>
    {
        var httpAccessor = ctx.GetService<IHttpContextAccessor>();
        if (httpAccessor.HttpContext == null)
            return new DummySchemaNameProvider();
        return new SchemaNameProvider(httpAccessor);
    });
    services.AddScoped<IMigrationsSqlGenerator, SchemaMigrationsSqlGenerator>();
    services.AddControllers();
    services.AddHttpContextAccessor();
}

My idea was that, on the design time (when I create migration) DummySchemaNameProvider would be supplied, as the IHttpContextAccessor won't have any context.

Implementation of DbContext:

public class AppDataContext : DbContext
{
    private readonly ISchemaNameProvider _schemaNameProvider;

    public AppDataContext(DbContextOptions<AppDataContext> options, ISchemaNameProvider schemaNameProvider) : base(options)
    {
        _schemaNameProvider = schemaNameProvider;
    }

    public DbSet<ToDoItem> ToDoItems { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        var schema = _schemaNameProvider.SchemaName;
        modelBuilder.HasDefaultSchema(schema);
    }
}

Unfortunately, when I try creating migration by running "Add-Migration Initial" (I have Microsoft.EntityFrameworkCore.Tools package installed), it end up with errors:

  1. If the SchemaMigrationsSqlGenerator is registered like in the linked tutorial:
opt.ReplaceService<IMigrationsSqlGenerator, SchemaMigrationsSqlGenerator>();

then the error is:

PM> add-migration Initial
Build started...
Build succeeded.
System.InvalidOperationException: Unable to resolve service for type 'EfSchemaPerTenantTest.ISchemaNameProvider' while attempting to activate 'EfSchemaPerTenantTest.SchemaMigrationsSqlGenerator'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c__DisplayClass7_0.<GetCallSite>b__0(Type type)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c__DisplayClass7_0.<GetCallSite>b__0(Type type)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c__DisplayClass7_0.<GetCallSite>b__0(Type type)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c__DisplayClass7_0.<GetCallSite>b__0(Type type)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c__DisplayClass7_0.<GetCallSite>b__0(Type type)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c__DisplayClass7_0.<GetCallSite>b__0(Type type)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Design.DesignTimeServiceCollectionExtensions.<>c__DisplayClass1_0.<AddDbContextDesignTimeServices>b__7(IServiceProvider _)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.EnsureServices(IServiceProvider services)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Unable to resolve service for type 'EfSchemaPerTenantTest.ISchemaNameProvider' while attempting to activate 'EfSchemaPerTenantTest.SchemaMigrationsSqlGenerator'.
  1. If it's registered the same way as in the linked SO thread:
services.AddScoped<IMigrationsSqlGenerator, SchemaMigrationsSqlGenerator>();

then the error is:

PM> add-migration Initial
Build started...
Build succeeded.
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.EntityFrameworkCore.Migrations.IMigrationsSqlGenerator Lifetime: Scoped ImplementationType: EfSchemaPerTenantTest.SchemaMigrationsSqlGenerator': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.Migrations.MigrationsSqlGeneratorDependencies' while attempting to activate 'EfSchemaPerTenantTest.SchemaMigrationsSqlGenerator'.)
Unable to create an object of type 'AppDataContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

Is this a bug or am I doing something so obviously wrong?

Steps to reproduce

  1. Implement IMigrationsSqlGenerator that inherits from SqlServerMigrationsSqlGenerator and that uses dependency injection through ctor.
  2. Create a migration.

Also you can check the attached project that reproduces the issue.

Further technical details

EF Core version: 3.1.5
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET Core 3.1
Operating system: Windows 10
IDE: Visual Studio 2019 16.5.5

@ajcvickers
Copy link
Member

/cc @bricelam Heads-up for triage

@ajcvickers
Copy link
Member

@Rudy102Michal IMigrationsSqlGenerator is resolved from the EF internal service provider. The discussion on #21578 should give you an idea on how to resolve a service from the application service inside an internal service.

@ajcvickers ajcvickers added the closed-no-further-action The issue is closed and no further action is planned. label Jul 31, 2020
@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported
Projects
None yet
Development

No branches or pull requests

2 participants