.NET Aspireに依存したMySQLとEntity FrameworkとASP.NET Coreでマイグレーションを作成する
環境
.NET AspireとEntity Frameworkでマイグレーション(ドキュメント的には移行)を実現するためのプロジェクト構成・コマンド例については公式ドキュメントがすでに存在する。
上記のドキュメントとほぼ同じように、ソリューションに.NET 9をランタイムとして設定した以下のプロジェクトがいる環境で実行する。
- .NET Aspireのプロジェクト (AppHost)
- ASP.NET Coreのプロジェクト (以下、
Web.Server
) - クラスライブラリのプロジェクト(DbContextとCode Firstのモデルが入っている。Migrationも入れたい。以下、
Web.Data
)
ただし公式ドキュメントと違う点は、利用するコネクタが
Aspire.Pomelo.EntityFrameworkCore.MySql
と
Pomelo.EntityFrameworkCore.MySql 9.0.0-preview.2.efcore.9.0.0
という点。
※Previewを使うのはまだEF9対応版が出ていないため。
問題
環境に記述した通り、すでに構成例となるドキュメントはすでに整備されている。
なので基本はこれをもとにプロジェクトを作成していけば、実行手前の段階までは持っていくことができる。
このドキュメントで詰まるのが「マイグレーションの作成」というところ。
ここで行うのが、一番最初のマイグレーションを作る作業。
そこでマイグレーションを作成すれば以後のMigrationService
が動作できるようになるのだが、次のエラーが出て作成することができない。
PS C:\…\Web\Web.Server> dotnet ef migrations add InitialCreate --project ..\..\Web.Data\Web.Data.csproj
Build started...
Build succeeded.
info: MySqlConnector.MySqlConnection[0]
Session 0.1 connect timeout expired connecting to IP address 172.21.48.1 for host name
System.Net.Sockets.SocketException (10038): ソケット以外のものに対して操作を実行しようとしました。 172.21.48.1:3306
at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.Sockets.Socket.Connect(EndPoint remoteEP)
at System.Net.Sockets.TcpClient.Connect(IPEndPoint remoteEP)
at MySqlConnector.Core.ServerSession.OpenTcpSocketAsync(ConnectionSettings cs, ILoadBalancer loadBalancer, Activity activity, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 1247
warn: Polly[3]
Execution attempt. Source: 'Microsoft.Extensions.Hosting.AspireEFMySqlExtensions.ServerVersion/(null)/Retry', Operation Key: '', Result: 'Connect Timeout expired.', Handled: 'True', Attempt: '0', Execution Time: 15031.7774ms
MySqlConnector.MySqlException (0x80004005): Connect Timeout expired.
---> MySqlConnector.MySqlException (0x80004005): Connect Timeout expired.
at MySqlConnector.Core.ServerSession.OpenTcpSocketAsync(ConnectionSettings cs, ILoadBalancer loadBalancer, Activity activity, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 1266
at MySqlConnector.Core.ServerSession.ConnectAsync(ConnectionSettings cs, MySqlConnection connection, Int64 startingTimestamp, ILoadBalancer loadBalancer, Activity activity, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 427
at MySqlConnector.Core.ServerSession.ConnectAndRedirectAsync(ILogger connectionLogger, ILogger poolLogger, IConnectionPoolMetadata pool, ConnectionSettings cs, ILoadBalancer loadBalancer, MySqlConnection connection, Action`4 logMessage, Int64 startingTimestamp, Activity activity, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 697
at MySqlConnector.Core.ServerSession.ConnectAndRedirectAsync(ILogger connectionLogger, ILogger poolLogger, IConnectionPoolMetadata pool, ConnectionSettings cs, ILoadBalancer loadBalancer, MySqlConnection connection, Action`4 logMessage, Int64 startingTimestamp, Activity activity, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 702
at MySqlConnector.MySqlConnection.CreateSessionAsync(ConnectionPool pool, Int64 startingTimestamp, Activity activity, Nullable`1 ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlConnection.cs:line 1099
at MySqlConnector.MySqlConnection.CreateSessionAsync(ConnectionPool pool, Int64 startingTimestamp, Activity activity, Nullable`1 ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlConnection.cs:line 1114
at MySqlConnector.MySqlConnection.OpenAsync(Nullable`1 ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlConnection.cs:line 567
at MySqlConnector.MySqlConnection.Open() in /_/src/MySqlConnector/MySqlConnection.cs:line 522
at Microsoft.EntityFrameworkCore.ServerVersion.AutoDetect(String connectionString)
at Microsoft.Extensions.Hosting.AspireEFMySqlExtensions.<>c__2`1.<AddMySqlDbContext>b__2_5(String cs) in /_/src/Components/Aspire.Pomelo.EntityFrameworkCore.MySql/AspireEFMySqlExtensions.cs:line 108
at Polly.ResiliencePipeline.<>c__36`2.<Execute>b__36_0(ResilienceContext _, ValueTuple`2 state)
または
PS C:\…\Web\Web.Server> dotnet ef migrations add InitialCreate --project ..\..\Web.Data\Web.Data.csproj
Build started...
Build succeeded.
The exception 'The string argument 'connectionString' cannot be empty.' was thrown while attempting to find 'DbContext' types. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
が表示されてマイグレーションが作成することができない。
接続タイムアウトの対処
Pomelo.EntityFrameworkCore.MySql
で用意されている機能として、接続先のサーバーバージョンによって発行するSQLを切り替えて互換性を維持する機能がある。
そのサーバーバージョンを決めるのに、手動設定で行うか自動設定で行うか選ぶことができる。
Aspire.Pomelo.EntityFrameworkCore.MySql
を使うと、指定しない場合は自動設定(ServerVersion.AutoDetect)になるのだが、この自動設定というのはいったんMySQL/MariaDBに接続してバージョン情報を取得するというもの。
dotnet ef migrations add
コマンドで実行されている場合、.NET Aspireを介しているわけではないので当然データベースは動作しておらず、接続できない。
そのため自動設定のための接続をしないために、手動でバージョン情報を渡してあげる必要がある。
ハードコーディングで回避するには、Web.Server
(ASP.NET Coreが動くプロジェクト、コマンドでカレントディレクトリにするもの)のProgram.cs
を以下のように変更する。
builder.AddMySqlDbContext<ApplicationDbContext>(
connectionName: "mariadb",
settings =>
{
settings.ServerVersion = "8.0.41";
});
または、appsettings.json
にバージョン情報を記述してそれを取得するようにすればよい。
接続文字列が空の対処
The exception 'The string argument 'connectionString' cannot be empty.'
というのは文字通り接続文字列が空だといわれている。
.NET Aspireから起動すれば、自動的に接続情報が注入されるため設定が必要ないのだが、dotnet ef migrations add
コマンドで実行されている場合、.NET Aspireを介しているわけではないので当然接続情報が注入されることはない。
手動でWeb.Server
(ASP.NET Coreが動くプロジェクト、コマンドでカレントディレクトリにするもの)のappsettings.json
に対して、以下のような設定を注入すればこの問題は回避できる。
{
"ConnectionStrings": {
"mariadb": "server=localhost;port=3306;database=dummydb;"
},
~省略~
}
あくまで設定が入っていれば良いのでDB自体にはつながらなくても大丈夫。
終わりに
SQL Serverは問題ないのに、MySQLにするとドキュメントに書いてない問題が発生するの、.NET Coreあるあるだよね。
Aspireの公式リポジトリでも.NET Aspire+EFの問題は議論されていて[1]、その結果上で使ったドキュメントができてはいるのですが、こうもう一声というか…。
Discussion