C# 8 introduced a new feature called nullable reference types (NRT), allowing reference types to be annotated, indicating whether it is valid for them to contain null or not. If you are new to this feature, it is recommended that make yourself familiar with it by reading the C# docs. A brief introduction to nullable reference type: you declare a variable to be a nullable reference type by appending a ? to the type. For example, string? represents a nullable string. You can use these new types to more clearly express your design intent: some variables must always have a value, others may be missing a value.

Required and optional properties

A property is considered optional if it is valid for it to contain null. If null is not a valid value to be assigned to a property then it is considered to be a required property. When mapping to a relational database schema, required properties are created as non-nullable columns, and optional properties are created as nullable columns.

  • If nullable reference types are disabled, all properties with .NET reference types are configured as optional by convention (for example, string).
  • If nullable reference types are enabled, properties will be configured based on the C# nullability of their .NET type: string? will be configured as optional, but string will be configured as required.

The following example shows an entity type with required and optional properties, with the nullable reference feature disabled and enabled:

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

    [Required] // Data annotations needed to configure as required
    public string Title { get; set; }

    [Required] // Data annotations needed to configure as required
    public string Author { get; set; }

    public string Category { get; set; } // Optional by convention
}

public class BlogWithNrt
{
    public int Id { get; set; }
    public string Title{ get; set; } // Required by convention
    public string Author{ get; set; } // Required by convention
    public string? Category{ get; set; } // Optional by convention

    // Note the following use of constructor binding, which avoids compiled warnings
    // for uninitialized non-nullable properties.
    public Customer(string title, string author, string? category= null)
    {
        Title= title;
        Author = author;
        Category= category;
    }
}

Using nullable reference types is recommended since it flows the nullability expressed in C# code to EF Core’s model and to the database, and obviates the use of the Fluent API or Data Annotations to express the same concept twice. The example above uses constructor binding to initialed the non-nullable properties. Constructor binding is a useful technique to ensure that your non-nullable properties are initialized. Unfortunately, in some scenarios constructor binding isn’t an option; navigation properties, for example, cannot be initialized in this way.

One way to deal with these scenarios, is to have a non-nullable property with a nullable backing field:

private string? _title;

public string Title
{
    set => _title = value;
    get => _title ?? throw new InvalidOperationException("Uninitialized property: " + nameof(Title));
}

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.

DbContext and DbSet

The common practice of having uninitialized DbSet properties on context types is also problematic, as the compiler will now emit warnings for them. This can be fixed as follows:

public class NullableReferenceTypesContext : DbContext
{
    public DbSet<Blog> Blogs => Set<Blog>();
}

As a terser alternative, it is possible to simply initialize the property to null with the help of the null-forgiving operator (!):

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

Navigating and including nullable relationships

When dealing with optional relationships, it’s possible to encounter compiler warnings where an actual null reference exception would be impossible. 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. 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. 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(blog.OptionalInfo!.ExtraAdditionalInfo);

A similar issue occurs when including multiple levels of relationships across optional navigations:

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

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. 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.