umbraco / Umbraco-CMS

Umbraco is a free and open source .NET content management system helping you deliver delightful digital experiences.
https://umbraco.com
MIT License
4.41k stars 2.66k forks source link

Entity Framework migrations broken from v12.2 (No database provider has been configured for this DbContext.) #14893

Closed AndyBoot closed 10 months ago

AndyBoot commented 11 months ago

Which Umbraco version are you using? (Please write the exact version, example: 10.1.0)

12.2

Bug summary

Upon implementing Entity Framework Core on Umbraco 12.2 as per the documentation (https://docs.umbraco.com/umbraco-cms/tutorials/getting-started-with-entity-framework-core) I'm receiving a DbContext error upon running a first migration. I've attempted the same thing on Umbraco 12.1.2 without fail.

Specifics

No response

Steps to reproduce

  1. Install Umbraco 12.2.
  2. Follow the steps at https://docs.umbraco.com/umbraco-cms/tutorials/getting-started-with-entity-framework-core
  3. Not required, but I've set the following in startup.cs instead:
services.AddUmbracoEFCoreContext<BlogContext>(
                _config.GetConnectionString("umbracoDbDSN") ?? "",
                _config.GetConnectionStringProviderName("umbracoDbDSN") ?? "");
  1. In a CLI, from the solution root run the following: dotnet ef migrations add InitialCreate --context BlogContext --project Website -v

  2. Result:

Using project '<path_to_solution>\Website\Website.csproj'.
Using startup project '<path_to_solution>\Website\Website.csproj'.
Writing '<path_to_solution>\Website\obj\Website.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=<path_to_user>\AppData\Local\Temp\tmp2D5C.tmp /verbosity:quiet /nologo "<path_to_solution>\Website\Website.csproj"
Writing '<path_to_solution>\Website\obj\Website.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=<path_to_user>\AppData\Local\Temp\tmp3423.tmp /verbosity:quiet /nologo "<path_to_solution>\Website\Website.csproj"
Build started...
dotnet build "<path_to_solution>\Website\Website.csproj" /verbosity:quiet /nologo

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:04.48
Build succeeded.
dotnet exec --depsfile "<path_to_solution>\Website\bin\Debug\net7.0\Website.deps.json" --additionalprobingpath <path_to_user>\.nuget\packages --runtimeconfig "<path_to_solution>\Website\bin\Debug\net7.0\Website.runtimeconfig.json" <path_to_user>\.dotnet\tools\.store\dotnet-ef\7.0.11\dotnet-ef\7.0.11\tools\net6.0\any\tools\netcoreapp2.0\any\ef.dll migrations add InitialCreate --context BlogContext --assembly "<path_to_solution>\Website\bin\Debug\net7.0\Website.dll" --project "<path_to_solution>\Website\Website.csproj" --startup-assembly "<path_to_solution>\Website\bin\Debug\net7.0\Website.dll" --startup-project "<path_to_solution>\Website\Website.csproj" --project-dir "<path_to_solution>\Website\\" --root-namespace Website --language C# --framework net7.0 --nullable --working-dir "<path_to_solution>" --verbose
Using assembly 'Website'.
Using startup assembly 'Website'.
Using application base '<path_to_solution>\Website\bin\Debug\net7.0'.
Using working directory '<path_to_solution>\Website'.
Using root namespace 'Website'.
Using project directory '<path_to_solution>\Website\'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider in assembly 'Website'...
Finding Microsoft.Extensions.Hosting service provider...
Using environment 'Development'.
Using application service provider from Microsoft.Extensions.Hosting.
Found DbContext 'UmbracoDbContext'.
Found DbContext 'BlogContext'.
Finding DbContext classes in the project...
Using context 'BlogContext'.
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<TContext> object in its constructor and passes it to the base constructor for DbContext.
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, DbContextOptions contextOptions, DbContext context)
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   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.Internal.DbContextOperations.CreateContext(Func`1 factory)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace)
   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)
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<TContext> object in its constructor and passes it to the base constructor for DbContext.

Expected result / actual result

With the same code and same installation steps using Umbraco 12.1.2, we have success:

dotnet ef migrations add InitialCreate --context BlogContext --project Website -v
Using project '<path_to_solution>\Code\Website\Website.csproj'.
Using startup project '<path_to_solution>\Code\Website\Website.csproj'.
Writing '<path_to_solution>\Code\Website\obj\Website.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=<path_to_user>\AppData\Local\Temp\tmp9264.tmp /verbosity:quiet /nologo "<path_to_solution>\Code\Website\Website.csproj"
Writing '<path_to_solution>\Code\Website\obj\Website.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=<path_to_user>\AppData\Local\Temp\tmp9822.tmp /verbosity:quiet /nologo "<path_to_solution>\Code\Website\Website.csproj"
Build started...
dotnet build "<path_to_solution>\Code\Website\Website.csproj" /verbosity:quiet /nologo
<path_to_solution>\Code\Website\RunDBMigrations.cs(32,17): warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call. [<path_to_solution>\Code\Website\Website.csproj]

Build succeeded.

<path_to_solution>\Code\Website\RunDBMigrations.cs(32,17): warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call. [<path_to_solution>\Code\Website\Website.csproj]
    1 Warning(s)
    0 Error(s)

Time Elapsed 00:00:05.21
Build succeeded.
dotnet exec --depsfile "<path_to_solution>\Code\Website\bin\Debug\net7.0\Website.deps.json" --additionalprobingpath <path_to_user>\.nuget\packages --runtimeconfig "<path_to_solution>\Code\Website\bin\Debug\net7.0\Website.runtimeconfig.json" <path_to_user>\.dotnet\tools\.store\dotnet-ef\7.0.11\dotnet-ef\7.0.11\tools\net6.0\any\tools\netcoreapp2.0\any\ef.dll migrations add InitialCreate --context BlogContext --assembly "<path_to_solution>\Code\Website\bin\Debug\net7.0\Website.dll" --project "<path_to_solution>\Code\Website\Website.csproj" --startup-assembly "<path_to_solution>\Code\Website\bin\Debug\net7.0\Website.dll" --startup-project "<path_to_solution>\Code\Website\Website.csproj" --project-dir "<path_to_solution>\Code\Website\\" --root-namespace Website --language C# --framework net7.0 --nullable --working-dir "<path_to_solution>\Code" --verbose
Using assembly 'Website'.
Using startup assembly 'Website'.
Using application base '<path_to_solution>\Code\Website\bin\Debug\net7.0'.
Using working directory '<path_to_solution>\Code\Website'.
Using root namespace 'Website'.
Using project directory '<path_to_solution>\Code\Website\'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider in assembly 'Website'...
Finding Microsoft.Extensions.Hosting service provider...
Using environment 'Development'.
Using application service provider from Microsoft.Extensions.Hosting.
Found DbContext 'UmbracoDbContext'.
Found DbContext 'BlogContext'.
Finding DbContext classes in the project...
Using context 'BlogContext'.
Finding design-time services referenced by assembly 'Website'...
Finding design-time services referenced by assembly 'Website'...
No referenced design-time services were found.
Finding design-time services for provider 'Microsoft.EntityFrameworkCore.SqlServer'...
Using design-time services from provider 'Microsoft.EntityFrameworkCore.SqlServer'.
Finding IDesignTimeServices implementations in assembly 'Website'...
No design-time services were found.
Writing migration to '<path_to_solution>\Code\Website\Migrations\20231002191659_InitialCreate.cs'.
Writing model snapshot to '<path_to_solution>\Code\Website\Migrations\BlogContextModelSnapshot.cs'.
Done. To undo this action, use 'ef migrations remove'
github-actions[bot] commented 11 months ago

Hi there @AndyBoot!

Firstly, a big thank you for raising this issue. Every piece of feedback we receive helps us to make Umbraco better.

We really appreciate your patience while we wait for our team to have a look at this but we wanted to let you know that we see this and share with you the plan for what comes next.

We wish we could work with everyone directly and assess your issue immediately but we're in the fortunate position of having lots of contributions to work with and only a few humans who are able to do it. We are making progress though and in the meantime, we will keep you in the loop and let you know when we have any questions.

Thanks, from your friendly Umbraco GitHub bot :robot: :slightly_smiling_face:

AndyBoot commented 11 months ago

This could be related to this PR which made it to the 12.2 release: https://github.com/umbraco/Umbraco-CMS/pull/14672

Cc: @nikcio @Zeegaan

nikcio commented 11 months ago

Hey @AndyBoot I don't think the PR you referenced above is the culprit but it might be this one instead: #14674

It was meant to make the DbContexts reactive to when a connection string changes like what happens on the Umbraco install and it might have broken registering custom DbContexts in the process.

We might need to figure out a different way to make sure that the connection string and provider is set correctly after an Umbraco install.

@bergmania asked me about a bit similar question not so long ago. Did you add the solution to the code base @bergmania? (The one with the custom DbContext pool)

Then we could test if we need to have the connection string and provider set on the constructor of the DbContext like was done in #14674 or setting it in the service collection extension is reactive when the connection string changes if that makes any sense.

AndyBoot commented 11 months ago

Hey @AndyBoot I don't think the PR you referenced above is the culprit but it might be this one instead: #14674

Ah my bad, I do apologies. Thankfully you knew where else to look though!

Let me know if I can help in anyway, such as testing.

bergmania commented 11 months ago

Yes, its merged here https://github.com/umbraco/Umbraco-CMS/pull/14852

AndyBoot commented 11 months ago

Working on another 12.2 project with the same issue. Thought I'd just share my workaround until this is patched.

  1. Ensure the site isn't running via IIS Express or any other instance.

  2. Forcefully revert your project(s) back to 12.1.2 using Find and Replace (be sure to 'Save All' files afterwards): image

  3. Generate your migration as before: dotnet ef migrations add MyMigration

  4. Manually run your dotnet ef database update command. Note that notification handler based approach will not also work under 12.2 as shown here: https://docs.umbraco.com/umbraco-cms/tutorials/getting-started-with-entity-framework-core#step-4-create-the-notification-handler

If you see the following output, then success it worked.

Build started...
Build succeeded.
Done.
  1. Then re-do the Find & Replace, but replace 12.1.2 with 12.2.0 instead.

  2. Build & run your site. As we've not actually run the site during the temporary downgrade you shouldn't hit any issues.

Hopefully that helps somebody during this period 😀

AleksandrRogov commented 11 months ago

Not sure if this helps someone or not, or even if the problem is the same: I did not have this error during migrations generation, but it happened whenever there was a request to my custom EF Context (I also use v12.2.0 (and updated from 12.1.1).

What I did is I used a different overload to register an ef core context:

Before:

builder.Services.AddUmbracoEFCoreContext<BlogContext>(connectionString, "Microsoft.Data.SqlClient");

After:

 builder.Services.AddUmbracoEFCoreContext<BlogContext>(
  (options, connectionString, providerName) => options.UseSqlServer(connectionString));

And it started working without issues. Hope it helps!

nikcio commented 11 months ago

@AleksandrRogov I was about to mention the same 😅 You can also use this approch if you are using SQLlite:

services.AddUmbracoEFCoreContext<TContext>("connectionString", "provider", (options, connectionString, provider) =>
{
    // For SQL Lite
    options.UseSqlite(connectionString);

   // For SQL Server
    options.UseSqlServer(connectionString);
});
AndyBoot commented 11 months ago

Thanks @AleksandrRogov & @nikcio, I can confirm that's fixed my issue.

If this fix is the intended configuration of a project going forward, is it worth an update to the previously linked documentation (https://docs.umbraco.com/umbraco-cms/tutorials/getting-started-with-entity-framework-core) to prevent others from running into the same issue, or could there be a fix implemented in a future hotfix to accomodate for both types of code based configuration?