Skip to content

Commit

Permalink
Improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
roji committed Sep 23, 2019
1 parent e14c0f8 commit c20799a
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 12 deletions.
16 changes: 9 additions & 7 deletions entity-framework/core/miscellaneous/nullable-reference-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This page introduces EF Core's support fo nullable reference types, and describe

If nullable reference types are enabled, EF Core will by convention configure reference properties as required or optional based on their C# type nullability. For example, properties with type `string` will be configured as required, while properties with type `string?` will be configured as optional.

[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Customer.cs?highlight=7-9)]
[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Customer.cs?name=Customer&highlight=4-6)]

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.

Expand All @@ -26,21 +26,23 @@ Using nullable reference types is recommended since it flows the nullability exp

When nullable reference types are enabled, the C# compiler emits warnings for any uninitialized non-nullable property, as these would contain null. As a result, the common practice of defining a non-nullable `DbSet` on a context will now generate a warning. However, EF Core always initializes all `DbSet` properties on DbContext-derived types, so they are guaranteed to never be null, even if the compiler is unaware of this. Therefore, it is recommended to keep your `DbSet` properties non-nullable - allowing you to access them without null checks - and to silence the compiler warnings by explicitly setting them to null with the help of the null-forgiving operator (!):

[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/NullableReferenceTypesContext.cs?highlight=7-8)]
[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/NullableReferenceTypesContext.cs?name=Context&highlight=3-4)]

## Non-nullable properties and initialization

Compiler warnings for uninitialized non-nullable reference types are also a problem for regular properties on your entity types. In our example above, we avoided these warnings by using [constructor binding](xref:core/modeling/constructors), 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](xref:core/querying/related-data)). 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.
Compiler warnings for uninitialized non-nullable reference types are also a problem for regular properties on your entity types. In our example above, we avoided these warnings by using [constructor binding](xref:core/modeling/constructors), 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](xref:core/querying/related-data)). 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.

One way to deal with these scenarios, is to have a non-nullable property with a nullable [backing field](xref:core/modeling/backing-field):

[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Order.cs?highlight=12-17)]
[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Order.cs?name=Order&highlight=16-11)]

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

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

[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Order.cs?highlight=19)]
[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Order.cs?name=Order&highlight=13)]

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.

Expand All @@ -51,11 +53,11 @@ An actual null value will never be observed except as a result of a programming

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:

[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Program.cs?highlight=46)]
[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Program.cs?range=31-46&highlight=16)]

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

[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Program.cs?highlight=37)]
[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Program.cs?range=31-46&highlight=7)]

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.

Expand Down
11 changes: 6 additions & 5 deletions entity-framework/core/modeling/required-optional.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@ Using nullable reference types is recommended since it flows the nullability exp
For more information on nullable reference types and how to use them with EF Core, [see the dedicated documentation page for this feature](xref:core/miscellaneous/nullable-reference-types).

## Data Annotations
## Configuration

You can use Data Annotations to indicate that a property is required.
You can indicate that a property is required as follows:

[!code-csharp[Main](../../../samples/core/Modeling/DataAnnotations/Required.cs?highlight=14)]
# [Data Annotations](#tab/data-annotations)

## Fluent API
[!code-csharp[Main](../../../samples/core/Modeling/DataAnnotations/Required.cs?highlight=14)]

You can use the Fluent API to indicate that a property is required.
# [Fluent API](#tab/fluent-api)

[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Required.cs?highlight=11-13)]

***
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace NullableReferenceTypes
{
#region Context
public class NullableReferenceTypesContext : DbContext
{
public DbSet<Customer> Customers { get; set; } = null!;
Expand All @@ -11,4 +12,5 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFNullableReferenceTypes;Trusted_Connection=True;ConnectRetryCount=0");
}
#endregion
}

0 comments on commit c20799a

Please sign in to comment.