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