Search code examples
asp.net-coreentity-framework-coreasp.net-core-5.0

How do resolve this exception about an ambiguous constructor in ASP.NET Core?


I use ASP.NET Core with .NET 5 and recently wanted to change from local development to Azure Web production mode.

Locally I use SQLite and everything works fine, on production I want to use Azure SQL. However when I want to migrate my database, I get a rather long exception:

System.Exception: Could not resolve a service of type 'Server.Calendars.CalendarDataContext' for the parameter 'calendarDataContext' of method 'Configure' on type 'Server.Startup'.
 ---> System.InvalidOperationException: Unable to activate type 'Server.Calendars.CalendarDataContext'. The following constructors are ambiguous:
Void .ctor(Microsoft.Extensions.Configuration.IConfiguration)
Void .ctor(Microsoft.EntityFrameworkCore.DbContextOptions`1[Server.Calendars.CalendarDataContext])
   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.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
   --- End of inner exception stack trace ---
   at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
   at Microsoft.AspNetCore.Hosting.ConfigureBuilder.<>c__DisplayClass4_0.<Build>b__0(IApplicationBuilder builder)
   at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass15_0.<UseStartup>b__1(IApplicationBuilder app)
   at Microsoft.Extensions.DependencyInjection.AutoRegisterMiddleware.<>c__DisplayClass4_0.<Configure>b__0(IApplicationBuilder app)
   at Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
   at Microsoft.AspNetCore.Server.IIS.Core.IISServerSetupFilter.<>c__DisplayClass2_0.<Configure>b__0(IApplicationBuilder app)
   at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app)
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)

My class CalendarDataContext .cs for Azure SQL

public class CalendarDataContext : DbContext
{
    public DbSet<CalendarEntry> CalendarEntries { get; set; }


    protected readonly IConfiguration Configuration;

    public CalendarDataContext(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public CalendarDataContext(DbContextOptions<CalendarDataContext> options)
        : base(options)
    { }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        if (!options.IsConfigured)
        {
            options.UseSqlServer(Configuration.GetConnectionString("CalendarDatabase"));
        }
    }
}

and CalendarDataContextSqlite.cs for SQLite

public class CalendarDataContextSqlite : CalendarDataContext
{
    public CalendarDataContextSqlite(IConfiguration configuration) : base(configuration) { }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        if (!options.IsConfigured)
        {
            var databaseName = Configuration.GetConnectionString("CalendarDatabase");
            var databasePath = PathHelper.DataPath(databaseName);
            options.UseSqlite("Data Source=" + databasePath);
        }
    }
}

I think the issue is with the line CalendarDataContext(DbContextOptions<CalendarDataContext> options) that I need for creating a temporary InMemory-Database for my tests.

How can I make this ambiguous constructor less ambiguous?

Edit: Add startup.cs

public class Startup
{
    public IWebHostEnvironment Environment { get; }

    public Startup(IConfiguration configuration, IWebHostEnvironment environment)
    {
        Environment = environment;
    }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        if (Environment.IsProduction())
        {
            services.AddDbContext<CalendarDataContext>();
        }
        else if (Environment.IsDevelopment())
        {
            services.AddDbContext<CalendarDataContext, CalendarDataContextSqlite>();
        }
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app,
        IWebHostEnvironment env,
        CalendarDataContext calendarDataContext)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        calendarDataContext.Database.Migrate();
    }
}

Solution

  • Firstly, Add IConfiguration as a local member in your Startup.cs

    IConfiguration Configuration;
    public IWebHostEnvironment Environment { get; }
    
    public Startup(IConfiguration configuration, IWebHostEnvironment environment)
    {
        Configuration = configuration;
        Environment = environment;
    }
    

    then Register a configured CalendarDataContext or CalendarDataContextSqlite in Startup

    public void ConfigureServices(IServiceCollection services)
    {
        if (Environment.IsProduction())
        {
            services.AddDbContext<CalendarDataContext>(options => 
                options.UseSqlServer(
                    Configuration.GetConnectionString("CalendarDatabase"));
        }
        else if (Environment.IsDevelopment())
        {
            services.AddDbContext<CalendarDataContext, CalendarDataContextSqlite>(options => {
                var databaseName = Configuration.GetConnectionString("CalendarDatabase");
                var databasePath = PathHelper.DataPath(databaseName);
                options.UseSqlite("Data Source=" + databasePath);
            });
        }
    }
    

    Then, CalendarDataContext:

    public class CalendarDataContext : DbContext
    {
        public DbSet<CalendarEntry> CalendarEntries { get; set; }
    
    
        public CalendarDataContext(DbContextOptions<CalendarDataContext> options)
            : base(options) { }
    
        protected CalendarDataContext(DbContextOptions options)
            : base(options) { }
    }
    

    And, CalendarDataContextSqlite:

    public class CalendarDataContextSqlite : CalendarDataContext
    {
        public CalendarDataContextSqlite(DbContextOptions<CalendarDataContextSqlite> options)
            : base(options) { }
    }
    

    Now,

    No need for the OnConfiguring in the context classes.

    In production you'll have a configured CalendarDataContext to be injected wherever the constructor asks for a CalendarDataContext.

    And for developerment you'll have a configured CalendarDataContextSqlite to be injected wherever the constructor asks for a CalendarDataContext.

    That configured context will also be injected into Startup.Configure so you can migrate your database.