Search code examples
c#entity-framework-core

Why does EF Core scaffold initialize non nullable nvarchar to "null!"?


In my database, I have a table:

CREATE TABLE [dbo].[Invitation]
(
    [Id] INT NOT NULL IDENTITY(1, 1),
    [Name] NVARCHAR(100) NOT NULL,
    [Subject] NVARCHAR(100) NOT NULL,
    [Body] NVARCHAR(max) NOT NULL,
    
    CONSTRAINT PK_Invitation PRIMARY KEY CLUSTERED ([Id] ASC),
)

I'm using EF Core 8.0.8. When I run dotnet ef dbcontext scaffold it creates the following C# class:

public partial class StudyInvitation
{
    public int Id { get; set; }

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

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

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

Why does it set the strings to null!? It seems like string.Empty would be a better choice for NOT NULL nvarchar. In code, setting a string to null and setting a string to null! will both set the string to null. So I don't understand why they chose to do this. I'd like to know so that when I'm working with my models I know if initializing them to null! is better than initializing them the string.Empty for some reason. Thanks!


Solution

  • If it did not use the null forgiveness you will end up with compiler warnings for uninitialized non-nullable reference types. (string) An alternative would have been to surround each of the properties with a warning disable:

    #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
        public string Name { get; set; }
    #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
    

    IMO this is a "noisy" option. The compiler warning can be completely disabled, or applied to the entire class but that negates the value it can have to avoid #null related bugs getting introduced.

    Alternatively they could have used a [Required] null-able property:

        [Required]
        public string? Name { get; set; }
    

    Personally not a fan of this option as when looking at values in Intelisense the null-ability of a property has an implied meaning of optional-ity.

    string.Empty or default might be a arguable default, though they likely wanted a consistent behavior across all reference types. Entities will generally be populated from the database by EF when reading rows, but still need to be constructed and comply with compiler rules & checks. Defaulting a string to string.Empty could hide potential problems until runtime when code forgets to set a non-null-able string. The DB will just receive an empty string which will wait until a tester, or worse, a user notices that records are being created without a required piece of information.

    My preferred solution is that my entities are normally declared in a DDD fashion for any code that needs to construct them which would have:

    public partial class StudyInvitation
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; protected set; }
    
        public string Name { get; set; }
    
        public string Subject { get; set; }
    
        public string Body { get; set; }
    
        public StudyInvitation(string name, string subject, string body)
        {
            ArgumentException.ThrowIfNullOrEmpty(name, nameof(name));
            ArgumentException.ThrowIfNullOrEmpty(subject, nameof(subject));
            ArgumentException.ThrowIfNullOrEmpty(body, nameof(body));
            
            Name = name;
            Subject = subject;
            Body = body;
       }
    }
    

    Where I'll usually avoid public setters entirely for required fields and use modifier methods to ensure "updating" a record has any related/required info provided and validated explicitly, no chance of partial state changes. This is what you might use for a normal class but EF won't always like parameterized constructors, so I also add:

    #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
        // Constructor for EF.
        protected StudyInvitation()
        { }
    #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
    

    For a non-DDD where you want to just rely on public setters you can use the same approach with a public default constructor to shut the compiler warning up about non-nullable reference type properties. The scaffolding just opted for the #null forgiveness. The database will still reject rows inserted without the required string.