[C# 8][Entity Framework Core] Play with Nullable reference types
Tags: csharp, entityframeworkcore
Published: 2020-07-29 16:32:27

## Intro
Because I have wanted to distinguish between nullable and non-null objects.
So I try "nullable reference types" that has been implemented in C# 8.0.

I use the project what I used last time.

### Environments
* .NET 5: ver.5.0.100-preview.7.20366.6
* Microsoft.EntityFrameworkCore: ver.5.0.0-preview.7.20365.15
* Microsoft.EntityFrameworkCore.Design: ver.5.0.0-preview.7.20365.15
* Npgsql.EntityFrameworkCore.PostgreSQL: ver.5.0.0-preview7-ci.20200722t163648
* Microsoft.EntityFrameworkCore.Abstractions: ver.5.0.0-preview.7.20365.15
* Microsoft.EntityFrameworkCore.Relational: ver.5.0.0-preview.7.20365.15
* Microsoft.AspNetCore.Mvc.NewtonsoftJson: ver.5.0.0-preview.7.20365.19

## Prepare
To use "nullable reference types" I must change the project language version to 8.0 or more and set "Nullable" enable.

#### CodeFirstSample.csproj
```xml
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <LangVersion>preview</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>
...
```

After setting "Nullable" enable, I get many warnings.
So I change my codes.

## About "nullable reference types"
Before C# 8, "value types" only can be nullable.

```csharp
int nonNullNumber = 0;
nonNullNumber = null; // <- compile error
int? nullableNumber = 0;
nullableNumber = null; // <- OK

// Nullable types must be cast first to assign
nonNullNumber = (int)nullableNumber;
```

After c# 8, I can distinguish reference type instances maybe null or not.

``` csharp
using System;
using Models;

namespace CSharpEightSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // Nullable instance
            User? nullableUser = null;  // <- OK
            // Non-nullable instance
            User user = null;           // <- Warning
        }
    }
}
```
Unfortunately I only can get warning when I assign null into Non-nullable instance.
It's for backward compatibility.

### IL
Are there any differences between Nullable instance and Non-nullable instance ?
I have a try with ILSpy Visual Studio Code Extension( https://github.com/icsharpcode/ilspy-vscode ).

```il
.class /* 02000005 */ private auto ansi beforefieldinit CSharpEightSample.Program
	extends [System.Runtime]System.Object
{
	// Methods
	.method /* 06000005 */ private hidebysig static 
		void Main (
			string[] args
		) cil managed 
	{
		.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
			01 00 01 00 00
		)
		// Method begins at RVA 0x2094
		// Code size 6 (0x6)
		.maxstack 1
		.entrypoint
		.locals /* 11000001 */ init (
			[0] class Models.User,
			[1] class Models.User
		)

		IL_0000: nop
		IL_0001: ldnull
		IL_0002: stloc.0
		IL_0003: ldnull
		IL_0004: stloc.1
		IL_0005: ret
	} // end of method Program::Main
...
```

For local instances, there are no any differences.
How about properties?

```csharp
namespace CSharpEightSample
{
    public class Sample
    {
        public string Name { get; set; } = "";
        public string? NullableName {get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}
```

```il
.class /* 02000005 */ public auto ansi beforefieldinit CSharpEightSample.Sample
	extends [System.Runtime]System.Object
{
	.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
		01 00 01 00 00
	)
	.custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
		01 00 00 00 00
	)
	// Fields
	.field /* 04000003 */ private string '<Name>k__BackingField'
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
		01 00 00 00 00 00 00 00
	)
	.field /* 04000004 */ private string '<NullableName>k__BackingField'
	.custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
		01 00 02 00 00
	)
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	.custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
		01 00 00 00 00 00 00 00
	)

	// Methods
	.method /* 06000005 */ public hidebysig specialname 
		instance string get_Name () cil managed 
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Method begins at RVA 0x2092
		// Code size 7 (0x7)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: ldfld string CSharpEightSample.Sample::'<Name>k__BackingField' /* 04000003 */
		IL_0006: ret
	} // end of method Sample::get_Name

	.method /* 06000006 */ public hidebysig specialname 
		instance void set_Name (
			string 'value'
		) cil managed 
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Method begins at RVA 0x209a
		// Code size 8 (0x8)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: ldarg.1
		IL_0002: stfld string CSharpEightSample.Sample::'<Name>k__BackingField' /* 04000003 */
		IL_0007: ret
	} // end of method Sample::set_Name

	.method /* 06000007 */ public hidebysig specialname 
		instance string get_NullableName () cil managed 
	{
		.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
			01 00 02 00 00
		)
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Method begins at RVA 0x20a3
		// Code size 7 (0x7)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: ldfld string CSharpEightSample.Sample::'<NullableName>k__BackingField' /* 04000004 */
		IL_0006: ret
	} // end of method Sample::get_NullableName

	.method /* 06000008 */ public hidebysig specialname 
		instance void set_NullableName (
			string 'value'
		) cil managed 
	{
		.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
			01 00 02 00 00
		)
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Method begins at RVA 0x20ab
		// Code size 8 (0x8)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: ldarg.1
		IL_0002: stfld string CSharpEightSample.Sample::'<NullableName>k__BackingField' /* 04000004 */
		IL_0007: ret
	} // end of method Sample::set_NullableName

	.method /* 06000009 */ public hidebysig specialname rtspecialname 
		instance void .ctor () cil managed 
	{
		// Method begins at RVA 0x20b4
		// Code size 19 (0x13)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: ldstr "" /* 70000001 */
		IL_0006: stfld string CSharpEightSample.Sample::'<Name>k__BackingField' /* 04000003 */
		IL_000b: ldarg.0
		IL_000c: call instance void [System.Runtime]System.Object::.ctor() /* 0A00000F */
		IL_0011: nop
		IL_0012: ret
	} // end of method Sample::.ctor

	// Properties
	.property /* 17000001 */ instance string Name()
	{
		.get instance string CSharpEightSample.Sample::get_Name()
		.set instance void CSharpEightSample.Sample::set_Name(string)
	}
	.property /* 17000002 */ instance string NullableName()
	{
		.custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
			01 00 02 00 00
		)
		.get instance string CSharpEightSample.Sample::get_NullableName()
		.set instance void CSharpEightSample.Sample::set_NullableName(string)
	}

} // end of class CSharpEightSample.Sample
```
"NullableName" has "System.Runtime.CompilerServices.NullableAttribute".

### default!
For example I have a non-nullable string instance.
I can assign empty string or "default!" as default value.

```csharp
string sample = ""; // <- OK
string sample2 = default! // <- OK
```

But what is assigned into "sample2"?
It's null value.

So I can use "default!" only for avoiding warnings.

### Generics
How about Generics?
I can't write like below.

```csharp
...
    public class Sample<T> where T: class
    {
        public T Name { get; set; } = default!;
        public T? NullableName {get; set; } // <- compile error
    }
...
```
If "T" has class or struct constraints, I can use "T?".

```csharp
...
    public class Sample<T> where T: class
    {
        public T Name { get; set; } = default!;
        public T? NullableName {get; set; } // <- OK
    }
...
```
Or I also can use "class ?" constraints.

```csharp
...
    public class Sample<T> where T: class ?
    {
        public T Name { get; set; } = default!;
        public T NullableName {get; set; } = default!;
    }
    class Program
    {
        static void Main(string[] args)
        {
            var s = new Sample<string?>(); // <- OK
        }
    }
...
```

* [Constraints on type parameters - C# Programming Guide | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters)
* [null 許容参照型 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C](https://ufcpp.net/study/csharp/resource/nullablereferencetype/)

Or I can use attributes.

```csharp
...
    public class Sample<T>
    {
        public T Name { get; set; } = default!;
        [AllowNull]
        public T NullableName {get; set; } = default!;
    }
    class Program
    {
        static void Main(string[] args)
        {
            var s = new Sample<string>();
            s.NullableName = null;          // <- OK
        }
...
```

I can use "[return:XXX]" attributes for methods.
```csharp
...
    public class Sample<T>
    {
        public T Name { get; set; } = default!;
        [AllowNull]
        public T NullableName {get; set; } = default!;
        [return: MaybeNull]
        public T GetNull()
        {
            return default(T);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var s = new Sample<string>();
            s.NullableName = null;
            Console.WriteLine((s.GetNull() == null));          // <- true
        }
...
```
* [C# Reserved attributes: Nullable static analysis | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/nullable-analysis)

## Entity FrameWork Core
### DbContext
In DbContext class, I can use "Set&lt;T>()" to avoid making "DbSet&lt;T>" instances nullable.

```csharp
using Microsoft.EntityFrameworkCore;

namespace Models
{
    public class CodeFirstSampleContext: DbContext
    {
        public CodeFirstSampleContext(DbContextOptions<CodeFirstSampleContext> options)
            : base(options)
        {

        }
        public DbSet<Workflow> Workflows => Set<Workflow>();
        public DbSet<WorkflowReader> WorkflowReaders => Set<WorkflowReader>();
    }
}
```
* [Working with nullable reference types - EF Core | Microsoft Docs](https://docs.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-types)

### Model
How about Model classes?

There aren't any special methods to avoid making them nullable.
So I should determine nullable or non-nullable from Database tables.

#### Does nullable/not-nullable reference types affect for migrations?
The answer is yes.

```csharp
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Models
{
    public class Sample
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        public string? Name { get; set; } = "hello";
    }
}
```


```csharp
...
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Samples",
                columns: table => new
                {
                    Id = table.Column<int>(type: "integer", nullable: false)
                        .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
                    Name = table.Column<string>(type: "text", nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Samples", x => x.Id);
                });
        }
...
```

"Name" is nullable though I set default value.

I still don't know if all properties should be non-null types as much as possible.
But I will try it first :)

## Resources
* [Nullable reference types | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references)
* [Design with nullable reference types | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/nullable-reference-types)
* [Nullable value types - C# reference | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-value-types)
* [Essential .NET - C# 8.0 and Nullable Reference Types | Microsoft Docs](https://docs.microsoft.com/en-us/archive/msdn-magazine/2018/february/essential-net-csharp-8-0-and-nullable-reference-types)