From 414a2fb0f5fe536632f527e992248d1600a9097e Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 15 Oct 2020 21:55:05 +0300 Subject: [PATCH] Tweak NRT guidance Fixes #2529 --- .../miscellaneous/nullable-reference-types.md | 22 +++++++++++++------ .../core/modeling/entity-properties.md | 8 +++---- .../NullableReferenceTypes/Customer.cs | 2 ++ .../CustomerWithConstructorBinding.cs | 15 +++++++++++++ .../CustomerWithWarning.cs | 13 +++++++++++ 5 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithConstructorBinding.cs create mode 100644 samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithWarning.cs diff --git a/entity-framework/core/miscellaneous/nullable-reference-types.md b/entity-framework/core/miscellaneous/nullable-reference-types.md index 092b0ccfa0..b5cdea351b 100644 --- a/entity-framework/core/miscellaneous/nullable-reference-types.md +++ b/entity-framework/core/miscellaneous/nullable-reference-types.md @@ -7,7 +7,7 @@ uid: core/miscellaneous/nullable-reference-types --- # Working with Nullable Reference Types -C# 8 introduced a new feature called [nullable reference types](/dotnet/csharp/tutorials/nullable-reference-types), 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. +C# 8 introduced a new feature called [nullable reference types (NRT)](/dotnet/csharp/tutorials/nullable-reference-types), 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. This page introduces EF Core's support for nullable reference types, and describes best practices for working with them. @@ -18,17 +18,17 @@ The main documentation on required and optional properties and their interaction > [!NOTE] > 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. When managing a relational database schema, this may cause migrations to be generated which alter the database column's nullability. -## DbContext and DbSet +## Non-nullable properties and initialization -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 having uninitialized DbSet properties on a context type will now generate a warning. This can be fixed as follows: +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 following, common way of writing entity types cannot be used: -[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/NullableReferenceTypesContext.cs?name=Context&highlight=3-4)] +[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithWarning.cs?name=CustomerWithWarning&highlight=4-5)] -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. The DbContext constructor ensures that all DbSet properties will get initialized, and null will never be observed on them. +[Constructor binding](xref:core/modeling/constructors) is a useful technique to ensure that your non-nullable properties are initialized: -## Non-nullable properties and initialization +[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithConstructorBinding.cs?name=CustomerWithConstructorBinding&highlight=6-9)] -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. +Unfortunately, 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. @@ -47,6 +47,14 @@ An actual null value will never be observed except as a result of a programming > [!NOTE] > Collection navigations, which contain references to multiple related entities, should always be non-nullable. An empty collection means that no related entities exist, but the list itself should never be null. +## 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: + +[!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/NullableReferenceTypesContext.cs?name=Context&highlight=3-4)] + +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. The DbContext base constructor ensures that all DbSet properties will get initialized, and null will never be observed on them. + ## 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: diff --git a/entity-framework/core/modeling/entity-properties.md b/entity-framework/core/modeling/entity-properties.md index a8861f9bf0..5e6238f00f 100644 --- a/entity-framework/core/modeling/entity-properties.md +++ b/entity-framework/core/modeling/entity-properties.md @@ -91,7 +91,7 @@ In the following example, configuring the `Score` property to have precision 14 #### [Data Annotations](#tab/data-annotations) -Currently not possible to use data annotations to configure. +Precision and scale cannot currently be configured via data annotations. #### [Fluent API](#tab/fluent-api) @@ -110,18 +110,18 @@ A property is considered optional if it is valid for it to contain `null`. If `n By convention, a property whose .NET type can contain null will be configured as optional, whereas properties whose .NET type cannot contain null will be configured as required. For example, all properties with .NET value types (`int`, `decimal`, `bool`, etc.) are configured as required, and all properties with nullable .NET value types (`int?`, `decimal?`, `bool?`, etc.) are configured as optional. -C# 8 introduced a new feature called [nullable reference types](/dotnet/csharp/tutorials/nullable-reference-types), which allows reference types to be annotated, indicating whether it is valid for them to contain null or not. This feature is disabled by default, and if enabled, it modifies EF Core's behavior in the following way: +C# 8 introduced a new feature called [nullable reference types (NRT)](/dotnet/csharp/tutorials/nullable-reference-types), which allows reference types to be annotated, indicating whether it is valid for them to contain null or not. This feature is disabled by default, and affects EF Core's behavior in the following way: * If nullable reference types are disabled (the default), 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 (the default) and enabled: -#### [Without nullable reference types (default)](#tab/without-nrt) +#### [Without NRT (default)](#tab/without-nrt) [!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithoutNullableReferenceTypes.cs?name=Customer&highlight=4-8)] -#### [With nullable reference types](#tab/with-nrt) +#### [With NRT](#tab/with-nrt) [!code-csharp[Main](../../../samples/core/Miscellaneous/NullableReferenceTypes/Customer.cs?name=Customer&highlight=4-6)] diff --git a/samples/core/Miscellaneous/NullableReferenceTypes/Customer.cs b/samples/core/Miscellaneous/NullableReferenceTypes/Customer.cs index c6a8000087..9a375d5b96 100644 --- a/samples/core/Miscellaneous/NullableReferenceTypes/Customer.cs +++ b/samples/core/Miscellaneous/NullableReferenceTypes/Customer.cs @@ -8,6 +8,8 @@ public class Customer public string LastName { get; set; } // Required by convention public string? MiddleName { get; set; } // Optional by convention + // Note the following use of constructor binding, which avoids compiled warnings + // for uninitialized non-nullable properties. public Customer(string firstName, string lastName, string? middleName = null) { FirstName = firstName; diff --git a/samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithConstructorBinding.cs b/samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithConstructorBinding.cs new file mode 100644 index 0000000000..da54e2065c --- /dev/null +++ b/samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithConstructorBinding.cs @@ -0,0 +1,15 @@ +namespace NullableReferenceTypes +{ + #region CustomerWithConstructorBinding + public class CustomerWithConstructorBinding + { + public int Id { get; set; } + public string Name { get; set; } + + public CustomerWithConstructorBinding(string name) + { + Name = name; + } + } + #endregion +} diff --git a/samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithWarning.cs b/samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithWarning.cs new file mode 100644 index 0000000000..6c07448f48 --- /dev/null +++ b/samples/core/Miscellaneous/NullableReferenceTypes/CustomerWithWarning.cs @@ -0,0 +1,13 @@ +#pragma warning disable CS8618 + +namespace NullableReferenceTypes +{ + #region CustomerWithWarning + public class CustomerWithWarning + { + public int Id { get; set; } + // Generates CS8618, uninitialized non-nullable property: + public string Name { get; set; } + } + #endregion +}