Skip to main content

How to Make EF Core Columns Required or Nullable with C# Nullable Reference Types

· 8 min read
Jagdish Kumawat
Founder @ Dewiride

Whether a database column may be empty should be decided once, in your C# code, and enforced everywhere: by the compiler, by EF Core and by MySQL. With nullable reference types turned on, EF Core does exactly that: string? becomes a column that allows NULL, string one that doesn't. This guide shows the rule in a real migration, adds C#'s required modifier so the compiler catches missing values, and changes an existing column from nullable to required without breaking.

The Rule​

EF Core's entity properties page states it: "If nullable reference types are enabled, properties will be configured based on the C# nullability of their .NET type: string? will be configured as optional, but string will be configured as required." And it recommends working this way: "Using nullable reference types is recommended since it flows the nullability expressed in C# code to EF Core's model and to the database".

In the database, that means: "required properties are created as non-nullable columns, and optional properties are created as nullable columns."

Prerequisites​

Step 1: See the Rule in a Migration​

A product entity mixes required and optional values:

src/Dewiride.Inventory.Data/Entities/Product.cs (excerpt)
public required string Sku { get; set; }

public required string Name { get; set; }

public string? Description { get; set; }

public decimal UnitPrice { get; set; }

public DateTime? ArchivedAtUtc { get; set; }

With no configuration at all, the first migration made:

Migrations/…_CreateCatalogTables.cs (excerpt)
Sku = table.Column<string>(type: "longtext", nullable: false),
Name = table.Column<string>(type: "longtext", nullable: false),
Description = table.Column<string>(type: "longtext", nullable: true),
UnitPrice = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
ArchivedAtUtc = table.Column<DateTime>(type: "datetime(6)", nullable: true),
  • string gives nullable: false, and string? gives nullable: true.
  • Value types such as decimal and DateTime can't be null in C#, so they're NOT NULL. Add ?, as in DateTime?, for an optional value: ArchivedAtUtc is empty until a product is archived.

No attributes and no configuration: the ? alone decided it.

Step 2: Let the Compiler Enforce It with required​

With nullable reference types on, a non-nullable string property with no value gets a compiler warning. I changed Description from string? to plain string:

Product.cs (a non-nullable string with no value)
public string Description { get; set; }
Output (dotnet build)
Dewiride.Inventory.Data net10.0 failed with 1 error(s) (0.1s)
/Users/you/projects/dewiride-inventory-api/src/Dewiride.Inventory.Data/Entities/Product.cs(15,19): error CS8618: Non-nullable property 'Description' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

It's an error here because the solution treats warnings as errors. EF Core's nullable reference types page explains the warning, and the fix it recommends: "If you're using C# 11 or above, required members provide the perfect solution to this problem". So:

Product.cs
public required string Description { get; set; }

required moves the check to every place that creates a product. Code that forgets it doesn't compile:

Output (dotnet build)
Dewiride.Inventory.Api net10.0 failed with 1 error(s) (0.9s)
/Users/you/projects/dewiride-inventory-api/src/Dewiride.Inventory.Api/Program.cs(95,23): error CS9035: Required member 'Product.Description' must be set in the object initializer or attribute constructor.

A terminal where dotnet build fails first with CS8618 for a non-nullable Description property without a value, then, after adding required, with CS9035 at the object initializer in Program.cs that doesn't set Description.

The docs describe the result: "The compiler now guarantees that when your code instantiates a Customer, it always initializes its Name property. And since the database column mapped to the property is non-nullable, any instances loaded by EF always contain a non-null Name as well." One keyword, three layers of protection. Fixed: error CS8618 covers the other ways to answer that warning.

Step 3: Change an Existing Column from Nullable to Required​

Making Description required changes the model, which EF Core notices:

Output (dotnet ef migrations has-pending-model-changes)
Changes have been made to the model since the last migration. Add a new migration.

The migration has a detail worth understanding:

Migrations/…_MakeDescriptionRequired.cs (Up, as generated)
migrationBuilder.AlterColumn<string>(
name: "Description",
table: "products",
type: "longtext",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true);

defaultValue: "" is there for the rows that already exist: they hold NULL, and a NOT NULL column can't keep them. On MySQL, though, this exact migration fails for a longtext column with "BLOB, TEXT, GEOMETRY or JSON column 'Description' can't have a default value". The fix is to fill the empty rows yourself and drop the default:

Migrations/…_MakeDescriptionRequired.cs (Up, edited)
protected override void Up(MigrationBuilder migrationBuilder)
{
// MySQL can't give a longtext column a literal default, so fill the empty
// rows first, then make the column NOT NULL without a default.
migrationBuilder.Sql("UPDATE `products` SET `Description` = '' WHERE `Description` IS NULL;");

migrationBuilder.AlterColumn<string>(
name: "Description",
table: "products",
type: "longtext",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true);
}
Output (dotnet ef database update, the lines that matter)
Applying migration '20261103044430_MakeDescriptionRequired'.
UPDATE `products` SET `Description` = '' WHERE `Description` IS NULL;
ALTER TABLE `products` MODIFY `Description` longtext NOT NULL;
Done.

The fixes post for that error shows the failure and two alternatives. With a varchar column, the generated migration works unchanged.

Explicit Configuration​

Nullable reference types are the recommended way, but two explicit forms exist, for example when a property is optional in C# but must be required in the database:

Fluent API, in the entity's configuration
builder.Property(p => p.Description).IsRequired();

or the [Required] attribute on the property. The docs present both: "A property that would be optional by convention can be configured to be required as follows".

Common Mistakes​

MistakeWhat happensDo this instead
Turning off nullable reference typesEvery string column becomes nullableKeep <Nullable>enable</Nullable>
Silencing CS8618 with = null! on entity propertiesCode can create entities without the value; MySQL refuses laterUse required
Making a column required without handling existing NULLsThe migration failsFill the rows in the migration first
Expecting required to validate API inputIt's a compile-time check on your codeValidate requests separately

Troubleshooting​

SymptomLikely causeFix
error CS8618: Non-nullable property …A non-nullable property without a valuerequired, an initializer, or ?
error CS9035: Required member … must be setCode creates the entity without a required valueSet it in the object initializer
A column is nullable but the property isn'tNullable reference types are off in that projectEnable them and create a migration

FAQ​

What about navigation properties like Category?​

A reference navigation that's always set by EF Core when loaded, such as public Category Category { get; set; } = null!;, is the one place null! is common: EF Core fills it, and the foreign key CategoryId is what decides whether the relationship is required.

Do DbSet properties need required?​

No. The docs explain that for DbSet properties, "EF Core 7.0 and above suppress this warning, since EF automatically initializes these properties via reflection."

Conclusion​

With nullable reference types on, the ? decides: string? and DateTime? become nullable columns, string and value types become NOT NULL. Add required to non-nullable strings so the compiler rejects code that forgets them, and when an existing column becomes required, fill its NULL rows in the migration before the ALTER, which on MySQL also avoids a longtext default.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.