dotnet ef migrations でエラーになった話

# 結論

- ASP.NET Core 2.2 アプリで `dotnet ef` コマンドを実行する場合は、 `Program.cs` に `CreateWebHostBuilder` メソッドが必要。
    - ここから EF Core CLI が ASP.NET Core アプリの情報 (DB 接続先とか) にアクセスする。
    - ただし、.NET Core 3.0 だと名前が変わるから注意！ (`CreateHostBuilder`)
- `dotnet ef` コマンドのエラー調査には `-v` オプション (`--verbose`) を使おう

# 事象

[ASP.NET Core & EFCore のチュートリアル](https://docs.microsoft.com/ja-jp/ef/core/get-started/aspnetcore/new-db?tabs=visual-studio)を触っていて、DB マイグレーションができなかった。
C# コードでモデルを定義し、 EF Core のマイグレーションコマンド (`dotnet ef migrations add InitialCreate`) を実行したところ、エラー発生。

```shell-session
$ dotnet ef migrations add InitialCreate
Unable to create an object of type 'BloggingContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
```

`-v` オプションで詳細表示したところ、原因が判明した。

```shell-session
$ dotnet ef migrations add InitialCreate -v
Using project 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\AspNetSample.csproj'.
Using startup project 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\AspNetSample.csproj'.
Writing 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\obj\AspNetSample.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\wukan\AppData\Local\Temp\tmpF769.tmp /verbosity:quiet /nologo C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\AspNetSample.csproj
Writing 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\obj\AspNetSample.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\wukan\AppData\Local\Temp\tmp24.tmp /verbosity:quiet /nologo C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\AspNetSample.csproj
dotnet build C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\AspNetSample.csproj /verbosity:quiet /nologo

ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:06.14
dotnet exec --depsfile C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\bin\Debug\netcoreapp3.0\AspNetSample.deps.json --additionalprobingpath C:\Users\wukan\.nuget\packages --additionalprobingpath "C:\Program Files\dotnet\sdk\NuGetFallbackFolder" --runtimeconfig C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\bin\Debug\netcoreapp3.0\AspNetSample.runtimeconfig.json "C:\Program Files\dotnet\sdk\3.0.100-preview3-010431\DotnetTools\dotnet-ef\3.0.0-preview3.19153.1\tools\netcoreapp3.0\any\tools\netcoreapp2.0\any\ef.dll" migrations add InitialCreate --assembly C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\bin\Debug\netcoreapp3.0\AspNetSample.dll --startup-assembly C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\bin\Debug\netcoreapp3.0\AspNetSample.dll --project-dir C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\ --language C# --working-dir C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample --verbose --root-namespace AspNetSample
Using assembly 'AspNetSample'.
Using startup assembly 'AspNetSample'.
Using application base 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\bin\Debug\netcoreapp3.0'.
Using working directory 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample'.
Using root namespace 'AspNetSample'.
Using project directory 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\'.
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider...
Finding IWebHost accessor...
No CreateWebHostBuilder(string[]) method was found on type 'AspNetSample.Program'.
No application service provider was found.
Finding DbContext classes in the project...
Found DbContext 'BloggingContext'.
Using context 'BloggingContext'.
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, IDbContextOptions contextOptions, DbContext context)
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.Internal.InternalAccessorExtensions.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)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_1.<.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.
```

`No CreateWebHostBuilder(string[]) method was found on type 'AspNetSample.Program'.` というメッセージに注目。どうやら、EF Core CLI は ASP.NET Core アプリケーションの `Program` クラスに定義されている (であろう) `CreateWebHostBuilder` メソッドを必要とする模様。これはプロジェクト作成時 (`dotnet new`) に自動生成されているハズ…なのだが、自分の環境では違う名前になっていたため、エラーが発生した。

## 解決方法

`Program.cs` に `CreateWebHostBuilder` メソッドをちゃんと定義し、

```csharp
public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateWebHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}
```

再度マイグレーションコマンド実行。

```shell-session
dotnet ef migrations add InitialCreate -v
Using project 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\AspNetSample.csproj'.
Using startup project 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\AspNetSample.csproj'.
Writing 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\obj\AspNetSample.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\wukan\AppData\Local\Temp\tmpA315.tmp /verbosity:quiet /nologo C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\AspNetSample.csproj
Writing 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\obj\AspNetSample.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\wukan\AppData\Local\Temp\tmpA9DD.tmp /verbosity:quiet /nologo C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\AspNetSample.csproj
dotnet build C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\AspNetSample.csproj /verbosity:quiet /nologo

ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:03.59
dotnet exec --depsfile C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\bin\Debug\netcoreapp3.0\AspNetSample.deps.json --additionalprobingpath C:\Users\wukan\.nuget\packages --additionalprobingpath "C:\Program Files\dotnet\sdk\NuGetFallbackFolder" --runtimeconfig C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\bin\Debug\netcoreapp3.0\AspNetSample.runtimeconfig.json "C:\Program Files\dotnet\sdk\3.0.100-preview3-010431\DotnetTools\dotnet-ef\3.0.0-preview3.19153.1\tools\netcoreapp3.0\any\tools\netcoreapp2.0\any\ef.dll" migrations add InitialCreate --assembly C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\bin\Debug\netcoreapp3.0\AspNetSample.dll --startup-assembly C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\bin\Debug\netcoreapp3.0\AspNetSample.dll --project-dir C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\ --language C# --working-dir C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample --verbose --root-namespace AspNetSample
Using assembly 'AspNetSample'.
Using startup assembly 'AspNetSample'.
Using application base 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\bin\Debug\netcoreapp3.0'.
Using working directory 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample'.
Using root namespace 'AspNetSample'.
Using project directory 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\'.
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider...
Finding IWebHost accessor...
Using environment 'Development'.
Using application service provider from IWebHost accessor on 'Program'.
Found DbContext 'BloggingContext'.
Finding DbContext classes in the project...
Using context 'BloggingContext'.
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 2.2.3-servicing-35854 initialized 'BloggingContext' using provider 'Pomelo.EntityFrameworkCore.MySql' with options: ServerVersion 8.0.15 MySql
Finding design-time services for provider 'Pomelo.EntityFrameworkCore.MySql'...
Using design-time services from provider 'Pomelo.EntityFrameworkCore.MySql'.
Finding design-time services referenced by assembly 'AspNetSample'.
No referenced design-time services were found.
Finding IDesignTimeServices implementations in assembly 'AspNetSample'...
No design-time services were found.
Writing migration to 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\Migrations\20190323131849_InitialCreate.cs'.
Writing model snapshot to 'C:\Users\wukan\Documents\work\dotnetcore\SqlPuzzle\src\AspNetSample\Migrations\BloggingContextModelSnapshot.cs'.
Done. To undo this action, use 'ef migrations remove'
```
成功！٩(๑´0\`๑)۶
`Using application service provider from IWebHost accessor on 'Program'.` とあるように、今度はちゃんと ASP.NET Core アプリケーションの情報を読み込んでくれた模様。

# 蛇足

この `CreateWebHostBuilder` というメソッド、 .NET Core 2.2 -> 3.0 で名前が変更になる (`CreateHostBuilder`)。よって .NET Core バージョンアップ時には注意が必要。

実は過去 (.NET Core 2.0 -> 2.1) にも名前が変わっている模様。
https://github.com/aspnet/Docs/issues/7087
名前が安定しない… (´・ω・｀)