9

When using the nullable-enable feature in Entity Framework Core model classes, the compiler will emit a lot of CS8618 warning like that:

warning CS8618: Non-nullable property 'Title' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Starting with C# 11, the new required modifier can be used (and no other workarounds are necessary) to get rid of the warnings:

class Book
{
    public int BookId { get; set; }

    public required string Title { get; set; } // <-- new required modifier
    public decimal Price { get; set; }
}

Now, my question is:

Should all properties get the new required modifier, even the value types?

In the example above, should Price also get the required modifier? Because actually, you never want the price to be 0 by default, but you always want it to be explicitely specified.

Should it be:

class Book
{
    public int BookId { get; set; }

    public required string Title { get; set; }
    public required decimal Price { get; set; }
}

If yes, then is it correct that the database id (here BookId) should NOT be required because it is generated only by the database in some cases?

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
eikuh
  • 673
  • 1
  • 9
  • 18
  • The reason I am asking this is the following: If the value type properties do not have the `required` modifier, it looks like (at first sight) as if they are not required. – eikuh Jul 19 '23 at 05:44
  • 1
    the "required" contextual keyword isn't really meant for specifying nullability status for EF with code-first approach – Emre Bener Jul 27 '23 at 12:31
  • The question is rather about "How to satisfy the compiler". However, the "required" modifier is even used in the official EF Core docs. – eikuh Jul 31 '23 at 06:15

3 Answers3

2

Because actually, you never want the price to be 0 by default, but you always want it to be explicitly specified. then is it correct that the database id (here BookId) should NOT be required because it is generated only by the database in some cases?

Yes, that is the distinguishing factor. Required modifier was created specifically for the cases when you want to ensure member initialization always (it is actually supported even by System.Text.Json during deserialization):

The required modifier indicates that the field or property it's applied to must be initialized by an object initializer

For properties which map to columns which can have database generated values (this actually is not limited to Ids, you can setup value generation for other columns too) you should avoid applying the required modifier for them, otherwise it could lead to a confusing API (and a lot of initializations like Id = default).

One thing which can help in some cases is detaching entity classes and DTOs/Models/Domain classes, so it is possible to have different models with different rules for different scenarios (i.e. model/DTO for creation does not require id initialization while one for update - does) and map between models and entities.

Also check out the Required and optional properties section of the EF Core Entity Properties doc.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Thanks for the post. While all you say is correct, you don't answer my question "Should all properties get the new required modifier, even the value types?" I was looking for a kind of official best approach. – eikuh Jul 31 '23 at 06:20
  • @eikuh _I was looking for a kind of official best approach._ - I would say that the official approach is summed up in the quote in the docs: if you have something which you want users (devs) to initialize on every instance creation - mark it as `required`. It is orthogonal to EF Core and value types and is basically up to you. – Guru Stron Jul 31 '23 at 06:24
  • @eikuh and yes, `required` will help with declarations like `public string SomeString {get; set;}` so there would be no warnings in such cases. But the same can be achieved with `public string SomeString {get; set;} = "";` (if empty string is appropriate value in your case) – Guru Stron Aug 01 '23 at 12:09
1

For specifying nullability status for entity properties, you can use "IsRequired" method which EF takes into consideration when creating migrations.

Here is an example entity class:

public class Album
{
    public string AlbumId { get; set; }
    public string AlbumName { get; set; }

    // Navigational Properties
    public string ArtistId { get; set; }
    public Artist Artist { get; set; }
    public string GenreId { get; set; }
    public Genre Genre { get; set; }
}

And here is the corresponding configuration class for this entity. I like to keep both of these classes in the same .cs file for better readability.

class AlbumConfiguration : IEntityTypeConfiguration<Album>
{
    public void Configure(EntityTypeBuilder<Album> builder)
    {
        builder.HasKey(a => a.AlbumId);
        builder.Property(a => a.AlbumId)
            .HasDefaultValueSql("gen_random_uuid()")
            .HasMaxLength(36);

        builder.Property(a => a.AlbumName).HasMaxLength(30);

        builder.Property(a => a.ArtistId).HasMaxLength(36).IsRequired(false);
        builder.Property(a => a.GenreId).HasMaxLength(36).IsRequired(false);
    }
}

You can set "IsRequired" to true if you want to make a field required, and vice versa.

In your db context class, inside OnModelCreating method, you should call "ApplyConfiguration" method, as otherwise the configuration class won't be taken into consideration by migrations.

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.ApplyConfiguration( new ArtistConfiguration() );
        builder.ApplyConfiguration( new GenreConfiguration() );
        builder.ApplyConfiguration( new AlbumConfiguration() );
    }

    public DbSet<Artist> Artist { get; set; }
    public DbSet<Genre> Genre { get; set; }
    public DbSet<Album> Album { get; set; }
}

That's all you need to do, and migrations will generate proper queries for you based on your configurations. I assume you know how to work with migrations, but let me know if you'd like me to elaborate on that as well.

Emre Bener
  • 681
  • 3
  • 15
  • _"Supressing the warnings don't work either for some reason"_ - because EF does not care if warnings are suppressed, it cares if NRTs are enabled. – Guru Stron Jul 27 '23 at 12:31
  • @GuruStron it seemed like a Visual Studio bug to me. EF worked fine with the warnings present. – Emre Bener Jul 27 '23 at 12:32
  • Maybe I misunderstood your statement. But in your case instead of `.IsRequired(false)` you could just do `public string? ArtistId...`, `public Artist? Artist ...` – Guru Stron Jul 27 '23 at 12:35
  • @GuruStron yes, that would work for strings, but if you do that with value type properties, VS spits out similar warnings. I find `IsRequired` to be the healthiest approach. – Emre Bener Jul 27 '23 at 12:37
  • Can you please give warnings it spits? – Guru Stron Jul 27 '23 at 12:38
  • I also remember EF ignoring the question mark (Or Nullable generic class) sometimes. Though I was working with Npgsql, and SqlServer provider may not have such issues. – Emre Bener Jul 27 '23 at 12:38
  • Thanks for the post. However, the code example either produces compiler warnings (if the nullable feature is enabled), or you don't use the nullable feature. But I want to use the nullable feature and at the same time have 0 warnings. – eikuh Jul 31 '23 at 06:18
  • @eikuh The point of `.IsRequired()` is to specify nullability status for columns without having to use question mark (nullable generic class) or any contextual keyword like "required", which seems to be what you are trying to achieve. This way, compiler won't have anything to complain about, and you will have what you want. Have you tried it? – Emre Bener Jul 31 '23 at 10:32
  • @EmreBener: But I do want to use the nullable-enable feature in my projects. – eikuh Aug 01 '23 at 07:50
0

The required modifier is used to explicitly indicate that non-nullable property should initialized when type instantiated. It is used for reference types mostly. In this example, it makes sense to use required for Title property.

It is common for BookId property to be primary key and autogenerated by databases, so, you can annotate it with the [Key] attribute to indicate that it is the primary key. Here's an example:

class Book
{
   [Key]
   public int BookId { get; set; }

   public required string Title { get; set; }

   public decimal Price { get; set; }
}
  • 1
    _"The required modifier is used to explicitly indicate that non-nullable property should first initialized with non-null value"_ - no, it does not, it means that property should be initialized when type is instantiated, the property still can be nullable and initialized with `null`. – Guru Stron Aug 01 '23 at 12:04
  • _"but, because Price is value type, you can not use this modifier for that."_ - this is not true. – Guru Stron Aug 01 '23 at 12:06
  • _"so, you can annotate it with the [Key] attribute to indicate that it is the primary key."_ - It is not required in EF Core to do this. – Guru Stron Aug 01 '23 at 12:06