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!
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!
1 Answer
Reset to default 4If 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.
null!
you explicitly said: even if the property is supposed to be non-nullable, it's okay to assignnull
to it. This is a null-forgiving operator, it tells you thatnull
is all right to use. See also another answer: What doesnull!
statement mean?. – Sergey A Kryukov Commented Feb 7 at 1:17string.Empty would be a better choice for NOT NULL nvarchar
that would be a very bad idea because it would hide missing values, bypassing the database constraint. Consider what would happen if you forgot to setName
in your code. You'd end up with a lot of invitations to `` – Panagiotis Kanavos Commented Feb 7 at 10:33=null!
is to make the propertyrequired
, iepublic required string Name { get; set; }
. That causes its own problems though, because you'd have to set allrequired
properties during initialization. This can be a problem eg in aNew Invitation
form whose controls bind to a newStudyInvitation
object. – Panagiotis Kanavos Commented Feb 7 at 10:40scaffold
command uses=null!
because right now that's the most generic option. Other options have implications. You can customize the code-generation templates if you want, to eg use constructor initialization or therequired
parameter or set the default you want. – Panagiotis Kanavos Commented Feb 7 at 10:48