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

dotnet-ef migrations add starts entire application when CreateHostBuilder method is not present #28105

Closed
marekott opened this issue May 26, 2022 · 14 comments

Comments

@marekott
Copy link


TLTR
When public static IHostBuilder CreateHostBuilder(string[] args) method is removed from Program.cs in a web api template project with dependencies on EF 6, generating migration with dotnet ef CLI tool starts the entire application instead of generating migration. In EF 5 this behaviour was not present.


Hello,
I've encountered strange behaviour of the dotnet ef CLI tool after updating my application to EF 6. Program.cs in our microservices looks like this:

public class Program
{
    public static Task Main(string[] args) => WebHostHelper.BuildAndRunWebHostAsync<Startup>(args, validateDiConfigurationInDebug: true);
}

As you can see it lacks CreateHostBuilder(string[] args) because it wouldn't be productive to add this method in all microservices. Instead, we provide a helper method as above. Also, what is important, our application is implementing IDesignTimeDbContextFactory in order to control creating of DbContext when migrations are being added (find simple reproduction here: https://github.com/marekott/dotnet-ef-add-migration-bug-reproduction). When application had dependency on EF 5 dotnet-ef migrations add Initial -v command produced valid migration (logs below). After updating to EF 6 the same command starts the entire application (logs below) that needs to be killed in order to finish producing migration (which in the end is a valid one). Adding CreateHostBuilder(string[] args) resolves this issue, application is not started and migration is generated. However, adding this method especially that it is not used and wasn't needed for 3 years seem as a lazy solution.

Taking everything into consideration what is a reason for this strange behaviour? It seems like a bug, especially that it has worked previously.

Logs for EF 5

PS C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi> dotnet-ef migrations add Initial
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
PS C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi> dotnet-ef migrations add Initial -v
Using project 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj'.
Using startup project 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj'.
Writing 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\obj\SampleWebApi.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\marek.ott\AppData\Local\Temp\tmp277A.tmp /verbosity:quiet /nologo C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj
Writing 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\obj\SampleWebApi.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\marek.ott\AppData\Local\Temp\tmp2A4A.tmp /verbosity:quiet /nologo C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj
Build started...
dotnet build C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj /verbosity:quiet /nologo

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

Time Elapsed 00:00:02.28
Build succeeded.
dotnet exec --depsfile C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\bin\Debug\net6.0\SampleWebApi.deps.json --additionalprobingpath C:\Users\marek.ott\.nuget\packages --runtimeconfig C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\bin\Debug\net6.0\SampleWebApi.runtimeconfig.json C:\Users\marek.ott\.dotnet\tools\.store\dotnet-ef\6.0.5\dotnet-ef\6.0.5\tools\netcoreapp3.1\any\tools\netcoreapp2.0\any\ef.dll migrations add Initial --assembly C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\bin\Debug\net6.0\SampleWebApi.dll --project C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj --startup-assembly C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\bin\Debug\net6.0\SampleWebApi.dll --startup-project C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj --project-dir C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\ --root-namespace SampleWebApi --language C# --framework net6.0 --working-dir C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi --verbose
Using assembly 'SampleWebApi'.
Using startup assembly 'SampleWebApi'.
Using application base 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\bin\Debug\net6.0'.
Using working directory 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi'.
Using root namespace 'SampleWebApi'.
Using project directory 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Found IDesignTimeDbContextFactory implementation 'PostgresDbContextMigrationsFactory'.
Found DbContext 'SampleWebApiContext'.
Finding application service provider in assembly 'SampleWebApi'...
Finding Microsoft.Extensions.Hosting service provider...
No static method 'CreateHostBuilder(string[])' was found on class 'Program'.
No application service provider was found.
Finding DbContext classes in the project...
Using DbContext factory 'PostgresDbContextMigrationsFactory'.
Using context 'SampleWebApiContext'.
Finding design-time services for provider 'Npgsql.EntityFrameworkCore.PostgreSQL'...
Using design-time services from provider 'Npgsql.EntityFrameworkCore.PostgreSQL'.
Finding design-time services referenced by assembly 'SampleWebApi'...
Finding design-time services referenced by assembly 'SampleWebApi'...
No referenced design-time services were found.
Finding IDesignTimeServices implementations in assembly 'SampleWebApi'...
No design-time services were found.
DetectChanges starting for 'SampleWebApiContext'.
DetectChanges completed for 'SampleWebApiContext'.
Writing migration to 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\Migrations\20220526065812_Initial.cs'.
Writing model snapshot to 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\Migrations\SampleWebApiContextModelSnapshot.cs'.
'SampleWebApiContext' disposed.
Done. To undo this action, use 'ef migrations remove'

Logs for EF 6

dotnet-ef migrations add Initial -v
Using project 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj'.
Using startup project 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj'.
Writing 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\obj\SampleWebApi.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\marek.ott\AppData\Local\Temp\tmp423C.tmp /verbosity:quiet /nologo C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj
Writing 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\obj\SampleWebApi.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\marek.ott\AppData\Local\Temp\tmp4460.tmp /verbosity:quiet /nologo C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj
Build started...
dotnet build C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj /verbosity:quiet /nologo

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

Time Elapsed 00:00:01.60
Build succeeded.
dotnet exec --depsfile C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\bin\Debug\net6.0\SampleWebApi.deps.json --additionalprobingpath C:\Users\marek.ott\.nuget\packages --runtimeconfig C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\bin\Debug\net6.0\SampleWebApi.runtimeconfig.json C:\Users\marek.ott\.dotnet\tools\.store\dotnet-ef\6.0.5\dotnet-ef\6.0.5\tools\netcoreapp3.1\any\tools\netcoreapp2.0\any\ef.dll migrations add Initial --assembly C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\bin\Debug\net6.0\SampleWebApi.dll --project C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj --startup-assembly C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\bin\Debug\net6.0\SampleWebApi.dll --startup-project C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\SampleWebApi.csproj --project-dir C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\ --root-namespace SampleWebApi --language C# --framework net6.0 --working-dir C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi --verbose
Using assembly 'SampleWebApi'.
Using startup assembly 'SampleWebApi'.
Using application base 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\bin\Debug\net6.0'.
Using working directory 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi'.
Using root namespace 'SampleWebApi'.
Using project directory 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Found IDesignTimeDbContextFactory implementation 'PostgresDbContextMigrationsFactory'.
Found DbContext 'SampleWebApiContext'.
Finding application service provider in assembly 'SampleWebApi'...
Finding Microsoft.Extensions.Hosting service provider...
Using environment 'Development'.
Hosting environment: Development
Content root path: C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi
Now listening on: http://localhost:5000
Now listening on: https://localhost:5001
Application started. Press Ctrl+C to shut down.
Application is shutting down...
PS C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi> System.InvalidOperationException: The entry point exited without ever building an IHost.
   at Microsoft.Extensions.Hosting.HostFactoryResolver.HostingListener.CreateHost()
   at Microsoft.Extensions.Hosting.HostFactoryResolver.<>c__DisplayClass10_0.<ResolveHostFactory>b__0(String[] args)
   at Microsoft.Extensions.Hosting.HostFactoryResolver.<>c__DisplayClass13_0.<ResolveServiceProviderFactory>b__3(String[] args)
   at Microsoft.EntityFrameworkCore.Design.Internal.AppServiceProviderFactory.CreateFromHosting(String[] args)
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: The entry point exited without ever building an IHost.
No application service provider was found.
Finding DbContext classes in the project...
Using DbContext factory 'PostgresDbContextMigrationsFactory'.
Using context 'SampleWebApiContext'.
Finding design-time services referenced by assembly 'SampleWebApi'...
Finding design-time services referenced by assembly 'SampleWebApi'...
No referenced design-time services were found.
Finding design-time services for provider 'Npgsql.EntityFrameworkCore.PostgreSQL'...
Using design-time services from provider 'Npgsql.EntityFrameworkCore.PostgreSQL'.
Finding IDesignTimeServices implementations in assembly 'SampleWebApi'...
No design-time services were found.
DetectChanges starting for 'SampleWebApiContext'.
DetectChanges completed for 'SampleWebApiContext'.
Writing migration to 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\Migrations\20220526070035_Initial.cs'.
Writing model snapshot to 'C:\Users\marek.ott\Desktop\demo\SampleWebApi\SampleWebApi\Migrations\SampleWebApiContextModelSnapshot.cs'.
'SampleWebApiContext' disposed.
Done. To undo this action, use 'ef migrations remove'

My dotnet ef version is:

Entity Framework Core .NET Command-line Tools
6.0.5
@ajcvickers
Copy link
Member

@marekott Running the application is an intentional change added by @davidfowl to support minimal APIs.

@davidfowl @bricelam Any thoughts here?

@bricelam
Copy link
Contributor

Related to #27306 (comment)

@plachor
Copy link

plachor commented May 27, 2022

That: #27322 (comment) would be best option for this case I believe. Since in docs you mentioned that:

... The generic hosting model is an alternative model that is supported indefinitely. The generic host underpins the new hosting model and is still the primary way to host worker-based applications.

Or allow to specify IDesignTimeDbContextFactory type explicitly in dotnet ef command instead of context.

@KrzysztofBranicki
Copy link

@davidfowl During application startup lot of things can be performed: microservice can send some message to the message bus indicating that it is started, it may register itself in Consul, some data seeding can take place. None of those is expected behavior when you are using EF tooling to generate new migration. Also application may require some mandatory configuration to be provided via env variables or Kubernetes mounted file (e.g. availability zone) without which it will throw, obviously you wouldn't want to provide this configuration just to generate EF migration. Long story short it is not expected behavior that adding database migration will start your application, but if you don't want to change existing behavior then at least add ability to directly specify in CLI IDesignTimeDbContextFactory (as @plachor suggested) which we have implemented exactly for the reason of constructingDbContext for the EF CLI tooling purpose.

@bricelam
Copy link
Contributor

bricelam commented Jun 7, 2022

I wonder if the Hosting code should have a switch (environment variable?) that avoids calling Main entirely. Seems like a good escape hatch to have...

@KrzysztofBranicki
Copy link

@bricelam I don't know what you have in mind when you say hosting code. If you mean that application should protect itself using some env variable from being unnecessarily started by EF tooling which just want to create migration then I think it is bad idea. BTW who should set this env variable? Whoever is executing migration before using dotnet ef? It should be rather part of dotnet ef to know whether it needs to start the app or just use IDesignTimeDbContextFactory which is explicitly provided for that reason. We may pass name of class that implements IDesignTimeDbContextFactory as a parameter to dotnet ef.

@davidfowl
Copy link
Member

@bricelam how does the IDesignTimeDbContextFactory support work? Does it need the service provider for that as well?

@ajcvickers
Copy link
Member

@KrzysztofBranicki This code is not intended to be used by just EF tooling, but by any tooling that needs to obtain the service provider. Therefore, while #27322 (comment) and #27306 (comment) are things we intend to do on the EF side to make this better, these things won't help other commands that also use this mechanism, hence it is worth thinking about something more general in hosting that could be used by someone using any command.

@davidfowl No, which is why #27322 (comment) is feasible. However, there is a chicken and egg problem in that if we don't know the registered context type (which might only be available via D.I.) then we might get the wrong factory type. #27322 (comment) recognizes that this is a corner case and that the value of assuming we have found the correct one to use is higher than the risk of getting it wrong in this corner case.

@KrzysztofBranicki
Copy link

@ajcvickers I understand that this code may be potentially used by some other tooling, assuming we would want to use any tooling that requires as to expose fully built web host with completely configured DI. My understanding was that whole purpose of IDesignTimeDbContextFactory<TDbContext> existence was so that if someone wants to opt out from this patter he can, just by implementing this factory which will create DbContext instance (because ultimately this is the only thing you need for EF).

I don't understand what you mean by chicken and egg problem. Currently dotnet ef accepts name of DbContext class as parameter so you know on which DbContext we want to operate, IDesignTimeDbContextFactory<TDbContext> is generic so you know which factory is for which DbContext. The worst case that can happen is if someone would have two factories for same DbContext type. Which would be strange and probably a bug so you can validate and produce proper error message. If you don't like idea with validation it was proposed also in this thread to pass the name of IDesignTimeDbContextFactory<TDbContext> implementing class as the CLI parameter instead of DbContext class name. Then there is no ambiguity on which DbContext type you want to operate and what should be used to create it.

@ajcvickers
Copy link
Member

ajcvickers commented Jun 13, 2022

@KrzysztofBranicki I agree that #27322 (comment) can be implemented, which is why the issue is open, not closed. I'm not sure what point you are trying to make.

@ajcvickers
Copy link
Member

Closing since EF work here is covered by other issues.

@olegbaslak
Copy link

Faced the same issue with the app start up, but it was a little bit tricky.

I use Autofac and have one service registered as IStartable (Autofac interface). It causes this service to start each time I run database update.

Changing IStartable interface with IHostedService and registering as HostedService in Startup.cs solved the issue. The service stopped executing each database update, preventing the script to finish.

@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
@deereeed
Copy link

deereeed commented Nov 29, 2022

Closing since EF work here is covered by other issues.

In which tasks exactly the problem will be solved?

@ajcvickers
Copy link
Member

@deereeed #27306 (comment) and #27322 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants