Null 許容参照型の使用

C# 8 では、Null 許容参照型 (NRT) と呼ばれる新機能が導入され、参照型の値として Null が有効かどうかを示す注釈を設定できるようになりました。 この機能を初めて使用する場合は、C# のドキュメントを参照して理解を深めてください。

このページでは、Null 許容参照型に対する EF Core のサポートについて説明し、これを使用するためのベスト プラクティスについて説明します。

必須および省略可能なプロパティ

必須および省略可能なプロパティと、Null 許容参照型とそれらの相互作用に関する主なドキュメントについては、「必須のプロパティと省略可能なプロパティ」のページを参照してください。 最初にこのページを読むことをお勧めします。

注意

既存のプロジェクトで Null 許容参照型を有効にする場合は注意が必要です。それまで省略可能として構成されていた参照型のプロパティは、明示的に Null 許容として注釈を設定しない限り、必須として構成されます。 リレーショナル データベースのスキーマを管理する際、このことが原因で、データベース列の Null 値の許容が変わってしまうような移行が生成されることがあります。

Null 非許容のプロパティと初期化

Null 許容参照型が有効になっている場合、C# コンパイラは、初期化されていない Null 非許容のプロパティに対し、これらが Null を含む可能性があるため警告を出力します。 そのため、次のような一般的なエンティティ型の記述方法は使用できません。

public class CustomerWithWarning
{
    public int Id { get; set; }

    // Generates CS8618, uninitialized non-nullable property:
    public string Name { get; set; }
}

コンストラクター バインドは、Null 非許容のプロパティが初期化されるようにするための便利な方法です。

public class CustomerWithConstructorBinding
{
    public int Id { get; set; }
    public string Name { get; set; }

    public CustomerWithConstructorBinding(string name)
    {
        Name = name;
    }
}

残念ながら、一部のシナリオではコンストラクター バインドを使用できません。たとえばナビゲーション プロパティは、この方法で初期化できません。

必須のナビゲーション プロパティではさらに難しくなります。特定のプリンシパルに対して依存関係が常に存在しますが、プログラムのその時点でのニーズに応じて、特定のクエリによって読み込まれたり、そうでなかったりすることがあります (データを読み込むためのさまざまなパターンを参照してください)。 同時に、これらのプロパティを Null 許容にすることは望ましくありません。そのようにすると、これらが必須であっても、これらへのすべてのアクセスで null のチェックを強制するためです。

これらのシナリオを処理する 1 つの方法は、Null 許容のバッキング フィールドと一緒に Null 非許容のプロパティを持つことです。

private Address? _shippingAddress;

public Address ShippingAddress
{
    set => _shippingAddress = value;
    get => _shippingAddress
           ?? throw new InvalidOperationException("Uninitialized property: " + nameof(ShippingAddress));
}

ナビゲーション プロパティは Null 非許容であるため、必須のナビゲーションが構成されます。また、ナビゲーションが適切に読み込まれている限り、依存関係にはプロパティを使用してアクセスできます。 ただし、最初に関連エンティティを適切に読み込まずにプロパティにアクセスした場合は、API コントラクトが正しく使用されなかったため、InvalidOperationException がスローされます。 EF は、プロパティではなく常にバッキング フィールドにアクセスするように構成する必要があることに注意してください。これは、設定が解除された場合でも値を読み取ることができるようにするためです。これを行う方法については、バッキング フィールドに関するドキュメントを参照し、構成が正しくなるようにするために、 を指定することを検討してください。

簡単な代替策として、null 免除演算子 (!) の助けを借りて、プロパティを単純に null に初期化することができます。

public Product Product { get; set; } = null!;

実際の Null 値が観察されることはありませんが、プログラミングのバグの結果として、たとえば関連するエンティティを事前に適切に読み込むことなくナビゲーション プロパティにアクセスするなどの場合は例外です。

注意

複数の関連エンティティへの参照を含むコレクション ナビゲーションは、常に Null 非許容にする必要があります。 空のコレクションは、関連するエンティティが存在しないことを意味しますが、リスト自体が null になることはありません。

DbContext と DbSet

初期化されていない DbSet プロパティをコンテキスト型で使用する一般的な方法も、コンパイラがそれらに対して警告を出力するようになったため、問題になります。 これは次のように修正できます。

public class NullableReferenceTypesContext : DbContext
{
    public DbSet<Customer> Customers => Set<Customer>();
    public DbSet<Order> Orders => Set<Order>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=EFNullableReferenceTypes;Trusted_Connection=True");
}

もう 1 つの方法として、Null 非許容の自動プロパティを使用しますが、それらを Null に初期化するために、Null 免除演算子 (!) を使用してコンパイラの警告をサイレント状態にします。 DbContext 基本コンストラクターによって、すべての DbSet プロパティが初期化され、それらについて Null が検出されることはありません。

省略可能なリレーションシップを処理するとき、実際の Null 参照例外が不可能な場合にコンパイラの警告が出される可能性があります。 LINQ クエリの変換と実行を行うとき、EF Core では、オプションの関連エンティティが存在しない場合は、その関連エンティティへのナビゲーションが単純に無視され、スローされないことが保証されます。 ただし、コンパイラではこの EF Core の保証が認識されないため、LINQ to Objects を使用して、LINQ クエリがメモリ内で実行された場合と同様に警告が生成されます。 結果として、Null 免除演算子 (!) を使用して、実際の Null 値が不可能であることをコンパイラに通知する必要があります。

Console.WriteLine(order.OptionalInfo!.ExtraAdditionalInfo!.SomeExtraAdditionalInfo);

オプションのナビゲーション全体で複数レベルのリレーションシップを含む場合にも、同様の問題が発生します。

var order = context.Orders
    .Include(o => o.OptionalInfo!)
    .ThenInclude(op => op.ExtraAdditionalInfo)
    .Single();

これを頻繁に実行し、問題のエンティティ型が EF Core クエリで主に (または独占的に) 使用されている場合は、ナビゲーション プロパティを Null 非許容にし、Fluent API またはデータ注釈を使用してこれらを省略可能として構成することを検討してください。 これにより、リレーションシップを省略可能にしたまま、すべてのコンパイラ警告が削除されます。ただし、EF Core の外部でエンティティを走査した場合、プロパティには Null 非許容として注釈が付けられていても、Null 値が観察されることがあります。

制限事項

  • リバース エンジニアリングでは C# 8 Null 許容参照型 (NRT) が現在サポートされない: EF Core では、この機能がオフであることが想定されている C# コードが常に生成されます。 たとえば、Null 値が許容されるテキスト列は、string? ではなく string 型のプロパティとしてスキャフォールディングされ、プロパティが必須かどうかを構成するために Fluent API またはデータ注釈のいずれかが使用されます。 スキャフォールディングされたコードを編集し、これらを C# の Null 許容の注釈に置き換えることができます。 Null 許容型参照型のスキャフォールディング サポートは、問題 #15520 によって追跡されています。
  • EF Core 6.0 より前では、パブリック API サーフェイスには Null 値の許容に対する注釈が付いていなかった (パブリック API は "Null 未指定" であった) ため、NRT 機能がオンになったときに使いにくい場合もあります。 これには特に、EF Core によって公開される非同期 LINQ 演算子 (たとえば FirstOrDefaultAsync) などがあります。 EF Core 6.0 以降では、パブリック API に Null 値の許容のための注釈が完全に付いています。