0
votes

THE BEGINNING

I've been trying to get a small project started so I can learn EF and creating APIs using C#. I'm following this tutorial for the API and followed this tutorial for EF setup. Initially, I was running across an issue creating my initial migration. This lead to me finding about creating a DbContextFactory. That worked, and I was able to create and run my initial migration. Now, however, I'm getting this error whenever I try calling my API route:

System.InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext. at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider() at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies() at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Internal.IDbContextDependencies.get_EntityFinderFactory() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.get_Finder() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.FindAsync(Object[] keyValues) at Games.Controllers.GameController.Get(Int32 id) in K:\Projects\c#\Games\Games\Controllers\GameController.cs:line 23 at lambda_method5(Closure , Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Before I get to the code, my project is setup so that I have the API project and the Database project in the same solution. The API project is the start up project, and is dependent on the Database project. I'm also using EF Core 5.0 and .NET 5.

THE CODE

GameDbContext.cs

using Games.Db.Tables;
using Microsoft.EntityFrameworkCore;

namespace Games.Db
{
    public class GameDbContext : DbContext
    {
        public GameDbContext(DbContextOptions<GameDbContext> options) : base(options)
        {

        }

        public DbSet<Game> Game { get; set; }
        public DbSet<Platform> Platform { get; set; }
        public DbSet<Medium> Medium { get; set; }
    }
}

GameDbContextFactory.cs

using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;

namespace Games.Db
{
    public class GameDbContextFactory : IDesignTimeDbContextFactory<GameDbContext>
    {
        public GameDbContext CreateDbContext(string[] args)
        {
            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("migrations.json")
                .Build();
            var optionsBuilder = new DbContextOptionsBuilder<GameDbContext>();
            var connectionString = configuration.GetConnectionString("MigrationsHelperConnection");
            optionsBuilder.UseSqlServer(connectionString);

            return new GameDbContext(optionsBuilder.Options);
        }
    }
}

Startup.cs

using Games.Db;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;

namespace Games
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddDbContext<GameDbContext>();

            services.AddTransient<DbContext, GameDbContext>();

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "Games", Version = "v1" });
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Games v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

WHAT I TRIED

I found this question, and added the OnConfiguring() to my GameDbContext, and created this:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var directory = Directory.GetCurrentDirectory();
        var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("migrations.json")
            .Build();
        var connectionString = configuration.GetConnectionString("MigrationsHelperConnection");
        optionsBuilder.UseSqlServer(connectionString);
    }

However, the error I get at this point is that it can't find the migrations.json. This is because it's looking the directory of my API project, and the file is in my Database project.

THE QUESTION

I'm obviously missing something very subtle in all this. Could be that it's almost 2am and the coffee ran out hours ago. But I'm completely stumped. What could I be missing?

EDIT

Here's a picture of solution layout:

enter image description here

1
Make sure migrations.json is copied to output folder after the build. It doesn't matter which project it comes from. And then make sure it is in the root folder of application and not in another folderAlexander
all the configuration files should be relative to the main project (in production) by being copied to the same folder with the main module or somewhere in the sub folders. Of course you need to design the folders structure beforehand and configure it accordingly in the code so that at runtime, it can look for the correct configuration files.King King
@Alexander: I updated so that the migrations.json goes to the output folder. The file is in the root folder for the Database project (though not for the solution or API project), and I'm still getting the same error.PiousVenom
Is API project referencing Database project? I tested such scenario and files from dependent projects are copied to the main project output. Also, are you checking file existence in {root}/bin/Debug/{target}/ folder? Make sure you are building project with certain configurations you are looking at.Alexander
@Alexander: Yes, the API project has a reference to the Database project. I've looked in the {root}/bin/Debug/net5.0, and verified the migrations.json is indeed there.PiousVenom

1 Answers

0
votes

After some prompting and guiding from Alexander, I updated my function to change the Directory:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 
        var configuration = new ConfigurationBuilder()
            .SetBasePath(directory)
            .AddJsonFile("migrations.json")
            .Build();
        var connectionString = configuration.GetConnectionString("MigrationsHelperConnection");
        optionsBuilder.UseSqlServer(connectionString);
    }