EntityFramework Core (以降、EF Core)のドキュメントを見ていたところ、C#8.0以降のnull許容参照型機能により、NOT NULL制約の付き方が変わるようでした。
必須およびオプションのプロパティ-EF Core | Microsoft Docs
そこで今回、null許容参照型機能の有効・無効ごとに、どんな感じになるのかを試してみました。
目次
環境
前回の記事の環境を、今回も引き続き使います。
null許容参照型に対する設定について
null許容参照型機能の有効化/無効化は、次のうちのどちらかにより可能です。
- プロジェクトファイル (*.csproj)
- プロジェクト全体の設定を変更
- nullable ディレクティブ
- ディレクティブがある箇所の設定を変更
プロジェクトファイル (*.csproj) での設定
以下のドキュメントに記載があります。
null 許容参照型を使用して設計する | Microsoft Docs
csprojファイルの中にある
- LangVersion
- Nullable
を、以下のように指定することで、null許容参照型機能が有効になります。
<PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <LangVersion>8</LangVersion> <Nullable>enable</Nullable> </PropertyGroup>
ちなみに、Rider IDEを使っている場合には、csprojファイルのプロパティから変更できます。
csprojファイルを右クリックし、コンテキストメニューから Properties...
を選択します。
ApplicationのLanguageの中に、設定箇所があります。
nullableディレクティブの場合
null許容参照型機能を一部だけ設定したい場合は、nullableディレクティブを使うことになります。
null 許容参照型 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
つまり
#nullable enable public class RqBlog4 { public int Id { get; set; } public string? Url { get; set; } } public class RqBlog5 { public int Id { get; set; } public string Url { get; set; } } #nullable restore
と書くと、RqBlog4・RqBlog5の中だけ、null許容参照型機能が有効になります。
ここまでで設定方法を見てきました。
次はnull許容参照型機能の有効/無効により、どのように動作が異なるかを確認してみます。
用意したモデルコード
以下の5種類を用意しました。
- RqBlog1クラス
- null許容参照型を使っていない場合
- RqBlog2クラス
- null許容参照型を使っていないが、
[Require]
Data Annotationを使っている場合
- null許容参照型を使っていないが、
- RqBlog3クラス、 RqPost3クラス
- null許容参照型を外部キーに使っていない場合
- RqBlog4クラス
- null許容参照型を使っている &
#nullable enable
を使っている場合
- null許容参照型を使っている &
- RqBlog5クラス
- null許容参照型を使っていない &
#nullable enable
を使っている場合
- null許容参照型を使っていない &
実際のソースコードはこんな感じです。
フィールド RqBlog*.Url
、もしくは外部キーの RqPost3.RqBlogFk
がどのように変化するかを見ます。
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace EFCoreRelationSample.Models { public class RqBlog1 { public int Id { get; set; } public string Url { get; set; } } public class RqBlog2 { public int Id { get; set; } [Required] public string Url { get; set; } } public class RqBlog3 { [Key] public string Url { get; set; } // コレクションナビゲーションプロパティ public List<RqPost3> Posts { get; set; } } public class RqPost3 { public int Id { get; set; } public string Content { get; set; } // 外部キー [ForeignKey("RqBlog3Url")] public string RqBlogFk { get; set; } // 参照ナビゲーションプロパティ public RqBlog3 Blog { get; set; } } #nullable enable public class RqBlog4 { public int Id { get; set; } public string? Url { get; set; } } public class RqBlog5 { public int Id { get; set; } public string Url { get; set; } } #nullable restore }
null許容参照型機能を無効にしたままの場合(デフォルト)
デフォルトのままで、マイグレーションを作成・適用した時のSQLを見てみます。
null許容参照型を使っていない場合
Urlには NOT NULL制約がありません。
info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqBlog1List" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_RqBlog1List" PRIMARY KEY AUTOINCREMENT, "Url" TEXT NULL );
null許容参照型を使っていないが、 [Require]
Data Annotationを使っている場合
Urlに、NOT NULL制約が付きました。
info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqBlog2List" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_RqBlog2List" PRIMARY KEY AUTOINCREMENT, "Url" TEXT NOT NULL );
null許容参照型を外部キーに使っていない場合
外部キーである RqBlogFk には、NOT NULL制約がありません。
# Blog info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqBlog3List" ( "Url" TEXT NOT NULL CONSTRAINT "PK_RqBlog3List" PRIMARY KEY ); # Post info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqPost3List" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_RqPost3List" PRIMARY KEY AUTOINCREMENT, "Content" TEXT NULL, "RqBlogFk" TEXT NULL, "BlogUrl" TEXT NULL, CONSTRAINT "FK_RqPost3List_RqBlog3List_BlogUrl" FOREIGN KEY ("BlogUrl") REFERENCES "RqBlog3List" ("Url") ON DELETE RESTRICT );
null許容参照型を使っている & #nullable enable
を使っている場合
Urlには NOT NULL制約がありません。
info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqBlog4List" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_RqBlog4List" PRIMARY KEY AUTOINCREMENT, "Url" TEXT NULL );
null許容参照型を使っていない & #nullable enable
を使っている場合
nullableディレクティブの影響により、null許可参照型機能が有効となり、UrlにNOT NULL制約が付きました。
info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqBlog5List" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_RqBlog5List" PRIMARY KEY AUTOINCREMENT, "Url" TEXT NOT NULL );
null許容参照型機能を有効にした場合
csprojファイルを編集し、null許容参照型機能を有効にしたあと、マイグレーションを作成・実行してみます。
null許容参照型を使っていない場合
先ほどとは異なり、UrlにNOT NULL制約が付きました。
info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqBlog1List" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_RqBlog1List" PRIMARY KEY AUTOINCREMENT, "Url" TEXT NOT NULL );
null許容参照型を使っていないが、 [Require]
Data Annotationを使っている場合
こちらは変わらず、UrlはNOT NULL制約が付いたままでした。
info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqBlog2List" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_RqBlog2List" PRIMARY KEY AUTOINCREMENT, "Url" TEXT NOT NULL );
null許容参照型を外部キーに使っていない場合
先ほどとは異なり、外部キー BlogUrl
にNOT NULL制約が付きました。
# Blog info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqBlog3List" ( "Url" TEXT NOT NULL CONSTRAINT "PK_RqBlog3List" PRIMARY KEY ); # Post info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqPost3List" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_RqPost3List" PRIMARY KEY AUTOINCREMENT, "Content" TEXT NOT NULL, "RqBlogFk" TEXT NOT NULL, "BlogUrl" TEXT NOT NULL, CONSTRAINT "FK_RqPost3List_RqBlog3List_BlogUrl" FOREIGN KEY ("BlogUrl") REFERENCES "RqBlog3List" ("Url") ON DELETE CASCADE );
null許容参照型を使っている & #nullable enable
を使っている場合
先程と同じく、UrlにはNOT NULL制約はありませんでした。
info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqBlog4List" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_RqBlog4List" PRIMARY KEY AUTOINCREMENT, "Url" TEXT NULL );
null許容参照型を使っていない & #nullable enable
を使っている場合
先程と同じく、UrlにはNOT NULL制約が付いていました。
info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] CREATE TABLE "RqBlog5List" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_RqBlog5List" PRIMARY KEY AUTOINCREMENT, "Url" TEXT NOT NULL );
以上より、null許容参照型機能を無効から有効に切り替えた場合は、実装方法によってはNULL許可からNOT NULL制約付与へと、意味が逆になるということが分かりました。
そのため、EF CoreにおけるNull 許容の参照型の使用については、
null 許容の参照型に対する EF Core のサポートについて説明し、それらを操作するためのベストプラクティスについて説明します。
と書かれているページを参照すると良さそうです。
ソースコード
GitHubに上げました。 EFCoreRelationSample/Models/NullableFunction.cs
が今回のファイルです。
https://github.com/thinkAmi-sandbox/ASP_Net_Core-sample