by WebSurfer
3. May 2023 15:32
多対多のリレーションシップを関連付ける中間テーブルへのナビゲーションプロパティ定義の問題で ASP.NET Core MVC での Create, Edit に失敗するのにハマって悩みました。またハマることがないよう備忘録として書いておきます。

上の画像は Microsoft のドキュメント「チュートリアル: ASP.NET MVC Web アプリでの EF Core の概要」のもので Enrollment が中間テーブルです。チュートリアルは Student テーブルの CRUD 操作を行う ASP.NET Core MVC アプリを作成するもので、これをこの記事の例に使います。
Visual Studio 2022 のテンプレートを使ってターゲットフレームワーク .NET 6.0 以降でプロジェクトを作ると、デフォルトでプロジェクト全体で「Null 許容」オプションが有効にされます。
「Null 許容」オプションが有効にされていると、チュートリアルの Student クラスの定義では LastName, FirstMiddleName, Enrollments プロパティ
に対しては "null 非許容のプロパティ 'xxxxx' には、コンストラクタの終了時に null 以外の値が入っていなければなりません" という警告が出ます。
その警告を回避するのに、以下のように = null!; を追加したのですが、ナビゲーションプロパティ Enrollments に対してそれはダメでした。ASP.NET Core MVC アプリによる CRUD 操作のうち Create, Edit に失敗します。
public class Student
{
public int ID { get; set; }
public string LastName { get; set; } = null!;
public string FirstMidName { get; set; } = null!;
public DateTime EnrollmentDate { get; set; }
// これはダメ。ASP.NET Core MVC での Create, Edit に失敗する
public ICollection<Enrollment> Enrollments { get; set; } = null!;
}
なぜ失敗するかと言うと、上の定義の場合 Enrollments が ModelStateDictionary に含まれるようになり、値が null になるので ValidationState が Invalid となってしまうからのようです。下の画像を見てください。結果、ModelSate.IsValid が false になって SaveChangesAsync メソッドがスキップされてしまいます。

チュートリアルの次のステップ「チュートリアル: CRUD 機能を実装する - ASP.NET MVC と EF Core」で、Edit アクションメソッドに TryUpdateModelAsync<T> メソッドを使う方法が紹介されていますが、それも失敗します。
解決策は、リバースエンジニアリングで既存の同等なデータベースから生成されるコードをまねて、Enrollments プロパティを以下のようにすることだと思います。
public ICollection<Enrollment> Enrollments { get; } =
new List<Enrollment>();
そうすれば、下の画像の通り ModelStateDictionary には Enrollments は含まれなくなります。Edit アクションメソッドに TryUpdateModelAsync<T> メソッドを使った場合も期待通りの結果となります。
