WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

参照型と ASP.NET Core の検証

by WebSurfer 2024年2月23日 17:49

.NET 6.0 以降の ASP.NET Core Web アプリで、コンテキストクラスとエンティティクラスを使って SQL Server などのデータベースのテーブルの CRUD 操作を行う際、エンティティクラス内の参照型のプロパティを null 許容にしておかないと検証に通らず、Create や Edit 操作に失敗するという話を書きます。特に下の画像のようなナビゲーションプロパティが要注意です。

検証結果が NG

また同じ問題にハマって時間を無駄にしないよう、どういうことかを書いておくことにしました。

先の記事「null 許容参照型と EF Core Code First」で書きましたように、Visual Studio 2022 のテンプレートを使って、ターゲットフレームワークを .NET 6.0 以降の設定にしてプロジェクトを作ると、デフォルトで「Null 許容」オプションが有効になり、Code First の場合は生成されるデータベースのフィールドの NULL 可/不可が、リバースエンジニアリングでデータベースからエンティティクラスを生成する場合はプロパティの型の null 許容/非許容が影響を受けます。

エンティティクラスをビューモデルに使ってブラウザからのデータを MVC アプリのアクションメソッドで受け取る場合、null 許容参照型に注意が必要です。特にナビゲーションプロパティの型による問題は気が付きにくいと思いました。

具体的にどういうことかを下の画像の SQL Server の Blogging データベースの dbo.Blogs, dbo.Posts テーブルを例に使って説明します。

SQL Server データベース

上の dbo.Blogs, dbo.Posts テーブルからリバースエンジニアリングでコンテキストクラスとエンティティクラスを生成します。ターゲットフレームワークは .NET 8.0 で「Null 許容」オプションはプロジェクト全体で有効に設定された状態です。パッケージマネージャコンソールから発行したコマンドを参考に下に載せておきます。

Scaffold-DbContext -Connection "接続文字列" -Provider Microsoft.EntityFrameworkCore.SqlServer -ContextDir Data -OutputDir Models -Tables Posts, Blogs -DataAnnotations

その結果、以下のエンティティクラスが生成されます。(同時にコンテキストクラスも生成されますが省略)

Blog クラス

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPages1.Models;

public partial class Blog
{
    [Key]
    public int BlogId { get; set; }

    public string Name { get; set; } = null!;

    [InverseProperty("Blog")]
    public virtual ICollection<Post> Posts { get; set; } = 
        new List<Post>();
}

Post クラス

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace RazorPages1.Models;

[Index("BlogId", Name = "IX_Posts_BlogId")]
public partial class Post
{
    [Key]
    public int PostId { get; set; }

    public string Title { get; set; } = null!;

    public string? Content { get; set; }

    public int BlogId { get; set; }
    
    [ForeignKey("BlogId")]
    [InverseProperty("Posts")]
    public virtual Blog Blog { get; set; } = null!;   
}

SQL Server の dbo.Blog テーブルの外部キーフィールド BlogId が NULL 不可になっているため、Blog クラスの Posts ナビゲーションプロパティと、Post クラスの Blog ナビゲーションプロパティの型は Null 許容になりません。

上の Post クラスをベースに、Visual Studio 2022 のスキャフォールディング機能を使って dbo.Posts テーブルの CRUD を行う ASP.NET Core MVC アプリのコントローラーとビューを生成し、アプリを実行してレコードの Create 操作をしようとしたところ ModelState.IsValid が false(検証結果 NG)となって失敗したというのがこの記事の一番上の画像です。Create 操作だけでなく、Edit 操作でも同様に検証 NG で失敗します。

検証 NG となった原因は、Post クラスで null 非許容のナビゲーションプロパティ Bolg に、モデルバインディングの際に null が代入されたからです。

なぜ null が代入されるかですが、下のスキャフォールディングで自動生成された Create 画面を見てください。ブラウザから送信されてくるのは Title, Content, BlogId だけということに注目してください。

Create 画面

一方 Create アクションメソッドの引数は、この記事の一番上の画像の通り Post クラスになっています。ブラウザからデータが送信されてこない PostId, Blog プロパティにはモデルバインディングの際その型のデフォルト値(参照型は null)が代入されます。一番上のデバッグ画面のローカル変数の値を見てください。

Blog ナビゲーションプロパティの型は null 非許容なので、ModelState.IsValid が false になり、Create, Edit に失敗します。

解決策は、生成されたコードに手を加えて Null 許容にすることです。

// 以下のように null 許容にすれば検証は通る
public virtual Blog? Blog { get; set; }

ちなみに、PostId もブラウザからは送信されてきませんのでモデルバインディングでデフォルト値が代入されますが、int 型のデフォルト値は null ではなくて 0 (ゼロ) なので問題は出ません。一番上の画像のローカル変数の青枠を見てください。

また、Title も null 非許容ですが、こちらは検証結果のエラーメッセージがブラウザの画面上の当該テキストボックスの下に表示されるので、すぐ気が付きます。Blog の方はエラーメッセージは出ませんので気づき難いです。

Tags: , , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar