EntityFramework Core 3.1 にて、null許容参照型機能の有効・無効ごとに、NOT NULL制約の状況を確認してみた

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... を選択します。

f:id:thinkAmi:20191219231313p:plain:w400

 
ApplicationのLanguageの中に、設定箇所があります。

f:id:thinkAmi:20191219231358p:plain:w400

 

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を使っている場合
  • RqBlog3クラス、 RqPost3クラス
    • null許容参照型を外部キーに使っていない場合
  • RqBlog4クラス
    • null許容参照型を使っている & #nullable enable を使っている場合
  • RqBlog5クラス
    • null許容参照型を使っていない & #nullable enable を使っている場合

 
実際のソースコードはこんな感じです。

フィールド 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を見てみます。

f:id:thinkAmi:20191219231508p:plain:w400

 

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許容参照型機能を有効にしたあと、マイグレーションを作成・実行してみます。

f:id:thinkAmi:20191219231529p:plain:w400  

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 のサポートについて説明し、それらを操作するためのベストプラクティスについて説明します。

Null 許容の参照型の使用-EF Core | Microsoft Docs

と書かれているページを参照すると良さそうです。

 

ソースコード

GitHubに上げました。 EFCoreRelationSample/Models/NullableFunction.cs が今回のファイルです。
https://github.com/thinkAmi-sandbox/ASP_Net_Core-sample