ASP.NET Core MVC アプリで PlanetScale をデーターベースに使ってみました。紆余曲折がありましたが何とか接続して使えるようになった結果が下の画像です。
その紆余曲折と言うか、アプリの作成で自分が遭遇した問題は以下の通りです。
-
EF Code First でテーブルを作成できない。
-
既存の DB からリバースエンジニアリングができない。
-
BeginTransaction メソッドが使えない。
その顛末を以下に備忘録として書いておきます。上の各項目についても説明します。
PlanetScale とは何かですが、"PlanetScale is the world’s most advanced serverless MySQL platform" ということだそうで、要するに MySQL を使ったクラウド上のデータベースサーバーらしいです。制約として外部キー制約がつけられないということがあるそうです。
利用するにあたって、まず、PlanetScale にサインインしてデータベース名と地域を設定し、データベースを作成する必要があります。以下の画像がその設定の例です。
データベースの作成に成功すると、以下のように自分が作成したデーターベース情報が表示されます。
上の画像の[Connect]ボタンをクリックすると、下の画像のように接続情報が表示されます。Username と Password が表示されるのは初回のみなので必ず記録しておいてください。
さらに、上の画像の赤枠の[.NET]を選択すると ASP.NET Core アプリの appsettings.json に設定するための接続文字列の例が表示されますので、これも忘れないように記録しておくことをお勧めします。
以上はデータベース作成時からデフォルトで存在している main ブランチでの話です。このあと開発作業用にブランチを追加するのが普通ということなので自分もそうしてみました。気をつけなければならないのは追加した開発作業用ブランチへの接続用の Username と Password は main ブランチのものとは異なることです。
自分はそれ��知らなくて半日ぐらいハマりました。開発作業用ブランチを作ってそれにテーブルを作成し、アプリからアクセスして SELECT クエリを投げたのですが、MySqlException がスローされてそのテーブルは見つからないと言われます。原因は Username と Password に main ブランチのものを使っていたので main ブランチに接続され、main ブランチにはそのテーブルは存在しないからでした。開発作業用ブランチに接続するには、開発作業用ブランチの Username と Password を使用する必要があるようです。
以上で PlanetScale 側のデータベースの準備は完了したはずなので、Visual Studio 2022 のテンプレートを使って .NET 6.0 の ASP.NET Core MVC のプロジェクトを新規に作成し、それから PlanetScale を使ってみます。
(1) EF Code First でテーブルを作成できない
最初に EF Code First の機能を使って PlanetScale にアプリが使うテーブルを作成してみます。先の記事「MySQL で Movie チュートリアル (CORE)」に書いた手順で、EF Code First の機能を利用して PlanetScale に Movie テーブルの作成をトライしてみました。
Add-Migration では問題なく Migration のためのクラスファイルが作成されます。しかし、Update-Database でのテーブルの作成に失敗します。
プロバイダに MySql.EntityFrameworkCore 6.0.10 を使った場合は、Update-Database の実行で KeyNotFoundException がスローされて失敗します。
Pomelo.EntityFrameworkCore.MySql 6.0.2 を使った場合は、ALTER DATABASE CHARACTER SET utf8mb4 というコマンドで失敗し "alter database is not supported" というエラーメッセージが出て、テーブルの作成に失敗します。
.NET 7.0 でも試してみましたが同じエラーで失敗します。
(2) リバースエンジニアリングができない
解決方法が分からないので、EF Code First による Movie テーブルの作成は諦めて、PlanetScale のサイトにアクセスして Console から手動で Movie テーブルを作成しました。下の画像の movie というのがそれです。
(注: __EFMigrationsHistory というテーブルもありますが、それは上に書いた EF Code First の操作でできたものです。その時は肝心の Movie テーブルは作成されていません)
先に作ったプロジェクトは放置して新たにゼロから ASP.NET Core MVC のプロジェクトを作成し、プロバイダには Pomelo.EntityFrameworkCore.MySql 6.0.2 を使って、リバースエンジニアリングでコンテキストクラスとエンティティクラスを作成をトライしました。
エラーなしで完了するもののコンテキストクラスしか生成されずその内容も不完全です。エンティティクラスは全く作成されません。
ちなみに、プロバイダに MySql.EntityFrameworkCore 6.0.10 を使った場合は KeyNotFoundException がスローされて何も生成されずに終わってしまいます。
これでは先に進めないので、不完全なコンテキストクラスは手直しして、エンティティクラスは自分でコードを書いて追加しました。その内容は以下の通りです。
コンテキストクラス
using Microsoft.EntityFrameworkCore;
using PlanetScaleMovie2.Models;
namespace PlanetScaleMovie2.Data
{
public partial class myplanetscaleContext : DbContext
{
public myplanetscaleContext()
{
}
public myplanetscaleContext(DbContextOptions<myplanetscaleContext> options)
: base(options)
{
}
// 以下の一行だけは自動生成されないので手動で追加
public virtual DbSet<Movie> Movies { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// 自動生成されたコードは不要なので削除
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseCollation("utf8mb4_0900_ai_ci")
.HasCharSet("utf8mb4");
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}
エンティティクラス
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace PlanetScaleMovie2.Models
{
[Table("movie")]
public partial class Movie
{
[Key]
public int Id { get; set; }
[Column(TypeName = "text")]
public string? Title { get; set; }
[Column(TypeName = "datetime")]
public DateTime ReleaseDate { get; set; }
[Column(TypeName = "text")]
public string? Genre { get; set; }
public decimal Price { get; set; }
}
}
そのあとスキャフォールディング機能を使って Controller と View を作成します。
Program.cs でコンテキストクラスを DI コンテナに登録します。
var serverVersion = new MySqlServerVersion(new Version(8, 0, 32));
var coonecctionString = builder.Configuration.GetConnectionString("PlanetScaleMovieContext");
builder.Services.AddDbContext<myplanetscaleContext>(options =>
options.UseMySql(coonecctionString, serverVersion));
appsettings.json に上のコードの "PlanetScaleMovieContext" という名前で接続文字列を追加します。Movie テーブル を開発作業用ブランチに作った場合は、接続文字列の user と password は開発作業用ブランチ用のものにすることに注意してください。
以上でアプリは今度は問題なく動きました。その結果がこの記事の一番上の画像です。
(3) BeginTransaction メソッドが使えない
これはプロバイダに Oracle 製の MySql.EntityFrameworkCore を使った場合です。以下のように BeginTransaction メソッドの行で KeyNotFoundException がスローされます。
プロバイダに Pomelo.EntityFrameworkCore.MySql を使った場合は問題ありません。Oracle 製はどうも相性が良くないようです。
なお、上記 (1), (2) の件については planetscale / discussion に Does PlanetScale support the .NET 6/7 EF Code First and reverse engineering? という質問を出しました。解決に向けて進展がありましたらこの記事に追記します。