Null 許容の参照型の使用Working with Nullable Reference Types

C# 8 では、null 値を 許容する参照型と呼ばれる新しい機能が導入され、参照型に注釈を付けて、null を含むかどうかを示すことができます。C# 8 introduced a new feature called nullable reference types, allowing reference types to be annotated, indicating whether it is valid for them to contain null or not. この機能を初めて使用する場合は、C# のドキュメントを参照してください。If you are new to this feature, it is recommended that make yourself familiar with it by reading the C# docs.

このページでは、null 許容の参照型に対する EF Core のサポートについて説明し、それらを操作するためのベストプラクティスについて説明します。This page introduces EF Core's support for nullable reference types, and describes best practices for working with them.

必須および省略可能なプロパティRequired and optional properties

必須およびオプションのプロパティに関する主なドキュメントと、null 許容の参照型との相互作用については、 必須プロパティと省略可能なプロパティ に関するページを参照してください。The main documentation on required and optional properties and their interaction with nullable reference types is the Required and Optional Properties page. まず最初にこのページを読むことをお勧めします。It is recommended you start out by reading that page first.

注意

既存のプロジェクトで null 値を許容する参照型を有効にする場合は注意してください。以前にオプションとして構成されていた参照型プロパティは、明示的に null 値が指定されていない限り、必須として構成されます。Exercise caution when enabling nullable reference types on an existing project: reference type properties which were previously configured as optional will now be configured as required, unless they are explicitly annotated to be nullable. リレーショナルデータベーススキーマを管理する場合、これにより、データベース列の null 値の許容属性を変更する移行が生成される可能性があります。When managing a relational database schema, this may cause migrations to be generated which alter the database column's nullability.

DbContext と DbcontextDbContext and DbSet

Null 値を許容する参照型が有効になっている場合、C# コンパイラは、初期化されていない null 非許容のプロパティに対して警告を出力します。When nullable reference types are enabled, the C# compiler emits warnings for any uninitialized non-nullable property, as these would contain null. その結果、コンテキスト型で初期化されていない DbSet プロパティを持つ一般的な方法では、警告が生成されるようになりました。As a result, the common practice of having uninitialized DbSet properties on a context type will now generate a warning. これは次のように修正できます。This can be fixed as follows:

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;ConnectRetryCount=0");
}

もう1つの方法として、null 非許容の自動プロパティを使用しますが、null に初期化するには、null を使用しない演算子 (!) を使用して、コンパイラの警告をサイレント状態にします。Another strategy is to use non-nullable auto-properties, but to initialize them to null, using the null-forgiving operator (!) to silence the compiler warning. DbContext コンストラクターによって、すべての Dbcontext プロパティが初期化され、null が検出されることはありません。The DbContext constructor ensures that all DbSet properties will get initialized, and null will never be observed on them.

Null 非許容のプロパティと初期化Non-nullable properties and initialization

初期化されていない null 非許容の参照型に対するコンパイラの警告は、エンティティ型の通常のプロパティにも問題があります。Compiler warnings for uninitialized non-nullable reference types are also a problem for regular properties on your entity types. 上記の例では、コンストラクターバインディングを使用して、これらの警告を回避しました。 コンストラクターバインドは、null 非許容のプロパティで完全に動作する機能であり、常に初期化されていることを保証します。In our example above, we avoided these warnings by using constructor binding, a feature which works perfectly with non-nullable properties, ensuring they are always initialized. ただし、シナリオによっては、コンストラクターバインドがオプションではない場合があります。たとえば、ナビゲーションプロパティをこの方法で初期化することはできません。However, in some scenarios constructor binding isn't an option: navigation properties, for example, cannot be initialized in this way.

必須のナビゲーションプロパティでは、特定のプリンシパルに依存関係が常に存在しますが、プログラムのその時点でのニーズに応じて、特定のクエリによって読み込まれないことがあります (データを読み込むためのさまざまなパターンを参照してください)。Required navigation properties present an additional difficulty: although a dependent will always exist for a given principal, it may or may not be loaded by a particular query, depending on the needs at that point in the program (see the different patterns for loading data). 同時に、これらのプロパティを null 許容にすることは望ましくありません。これは、必要に応じて、これらのプロパティへのすべてのアクセスが null をチェックするようにするためです。At the same time, it is undesirable to make these properties nullable, since that would force all access to them to check for null, even if they are required.

これらのシナリオを処理する方法の1つは、null 許容の バッキングフィールドを持つ null 非許容プロパティを持つことです。One way to deal with these scenarios, is to have a non-nullable property with a nullable backing field:

private Address? _shippingAddress;

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

ナビゲーションプロパティは null 非許容であるため、必要なナビゲーションが構成されます。また、ナビゲーションが適切に読み込まれている限り、依存するにはプロパティを使用してアクセスできます。Since the navigation property is non-nullable, a required navigation is configured; and as long as the navigation is properly loaded, the dependent will be accessible via the property. ただし、最初に関連エンティティを適切に読み込むことなく、プロパティにアクセスした場合は、API コントラクトが正しく使用されていないため、InvalidOperationException がスローされます。If, however, the property is accessed without first properly loading the related entity, an InvalidOperationException is thrown, since the API contract has been used incorrectly. EF は、プロパティではなく、常にバッキングフィールドにアクセスするように構成する必要があることに注意してください。これは、設定が解除された場合でも値を読み取ることができるようにするためです。これを行う方法については、 バックアップフィールド に関するドキュメントを参照してください。を指定して PropertyAccessMode.Field 、構成が正しいことを確認することを検討してください。Note that EF must be configured to always access the backing field and not the property, as it relies on being able to read the value even when unset; consult the documentation on backing fields on how to do this, and consider specifying PropertyAccessMode.Field to make sure the configuration is correct.

Terser として、null に対応していない演算子 (!) のヘルプを使用して、単純にプロパティを null に初期化することができます。As a terser alternative, it is possible to simply initialize the property to null with the help of the null-forgiving operator (!):

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

実際の null 値は、プログラミングのバグの結果としては決して観察されません。たとえば、関連エンティティを事前に適切に読み込むことなくナビゲーションプロパティにアクセスする場合などです。An actual null value will never be observed except as a result of a programming bug, e.g. accessing the navigation property without properly loading the related entity beforehand.

注意

複数の関連エンティティへの参照を含むコレクションナビゲーションは、常に null 非許容にする必要があります。Collection navigations, which contain references to multiple related entities, should always be non-nullable. 空のコレクションは、関連するエンティティが存在しないことを意味しますが、リスト自体を null にすることはできません。An empty collection means that no related entities exist, but the list itself should never be null.

オプションのリレーションシップを処理する場合、実際の null 参照例外が発生しないというコンパイラの警告が表示される可能性があります。When dealing with optional relationships, it's possible to encounter compiler warnings where an actual null reference exception would be impossible. LINQ クエリの変換と実行時に、オプションの関連エンティティが存在しない場合は、スローされるのではなく、その関連エンティティへのナビゲーションが無視されることが EF Core 保証されます。When translating and executing your LINQ queries, EF Core guarantees that if an optional related entity does not exist, any navigation to it will simply be ignored, rather than throwing. ただし、コンパイラはこの EF Core 保証を認識しないため、LINQ クエリがメモリ内で実行されたかのように、LINQ to Objects を使用して警告を生成します。However, the compiler is unaware of this EF Core guarantee, and produces warnings as if the LINQ query were executed in memory, with LINQ to Objects. 結果として、null を許容しない演算子 (!) を使用して、実際の null 値が不可能であることをコンパイラに通知する必要があります。As a result, it is necessary to use the null-forgiving operator (!) to inform the compiler that an actual null value isn't possible:

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

次のような場合にも同様の問題が発生します。A similar issue occurs when including multiple levels of relationships across optional navigations:

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

これを頻繁に実行し、問題のエンティティ型が EF Core クエリで主に (または排他的に) 使用されている場合は、ナビゲーションプロパティを null 非許容にし、Fluent API またはデータ注釈を使用してオプションとして構成することを検討してください。If you find yourself doing this a lot, and the entity types in question are predominantly (or exclusively) used in EF Core queries, consider making the navigation properties non-nullable, and to configure them as optional via the Fluent API or Data Annotations. これにより、リレーションシップを省略可能にしたまま、すべてのコンパイラ警告が削除されます。ただし、EF Core の外部でエンティティを走査した場合、プロパティには null 非許容として注釈が付けられますが、null 値を確認することができます。This will remove all compiler warnings while keeping the relationship optional; however, if your entities are traversed outside of EF Core, you may observe null values although the properties are annotated as non-nullable.

制限事項Limitations

  • リバースエンジニアリングでは、現在、 C# 8 の null 許容参照型 (NRTs)はサポートされていません。 EF Core は、機能がオフであることを前提とする c# コードを常に生成します。Reverse engineering does not currently support C# 8 nullable reference types (NRTs): EF Core always generates C# code that assumes the feature is off. たとえば、null 値が許容されるテキスト列は、 string string? プロパティが必須かどうかを構成するために使用される Fluent API またはデータ注釈を使用してではなく、型のプロパティとしてスキャフォールディングされます。For example, nullable text columns will be scaffolded as a property with type string , not string?, with either the Fluent API or Data Annotations used to configure whether a property is required or not. スキャフォールディングコードを編集し、C# の Null 値の許容属性に置き換えることができます。You can edit the scaffolded code and replace these with C# nullability annotations. Null 許容型参照型のスキャフォールディングサポートは、 #15520問題によって追跡されます。Scaffolding support for nullable reference types is tracked by issue #15520.
  • EF Core のパブリック API サーフェイスには、null 値の許容属性 (パブリック API が "無関係") に対してまだ注釈が付けられていないため、NRT 機能が有効になっているときに使用するのが困難な場合があります。EF Core's public API surface has not yet been annotated for nullability (the public API is "null-oblivious"), making it sometimes awkward to use when the NRT feature is turned on. これには、特に、EF Core によって公開される非同期 LINQ 演算子 (たとえば、「」を含む) が含まれます。This notably includes the async LINQ operators exposed by EF Core, such as FirstOrDefaultAsync. 5.0 リリースでは、このことに対処する予定です。We plan to address this for the 5.0 release.