Diagnosing Unexpected NOT NULL Columns in EF Core Owned Type Migrations
EF Core owned types default to required; this diagnostic guide helps you identify why migrations scaffold NOT NULL columns and how to fix them with .IsRequired(false) and verification steps.
11 Jan 2026, 21:42 UTC

You added an owned type to your EF Core model expecting it to live inline with its parent, but the migration scaffolded a NOT NULL column—and either the migration failed or the application threw an exception when trying to insert a row without that property. This is a classic mismatch between EF Core’s default owned‑type convention and the optional semantics you intended.
Recognizable condition
You ran dotnet ef migrations add InitialCreate and observed one of the following:
- The migration file contains a column definition with
NOT NULLfor a property that represents an embedded object you expected to be optional. - An
InvalidOperationExceptionis thrown at runtime whenSaveChangesAsyncis called because a null value cannot be inserted into a NOT NULL column. - The database schema shows a wider‑than‑expected row, which can impact index selectivity and overall query performance.
| Cause | Symptom | Check |
|---|---|---|
Owned properties default to required unless .IsRequired(false) is explicitly set. | Migration scaffolds NOT NULL on the owned column. | Inspect the .OwnsOne() call in your DbContext.OnModelCreating or class‑level configuration. |
Owned type’s columns participate in the parent’s concurrency token if rowversion is configured. | Unexpected update statements include owned columns, causing concurrency conflicts. | Check your ConfigureConcurrencyToken or RowVersion configuration and verify whether owned columns are intended to be part of that token. |
| Mixing owned types with TPH or TPT inheritance hierarchies is not supported. | Schema generation throws or produces duplicate/conflicting columns. | Review your inheritance map: if any entity uses [Table-per-Hierarchy] or [Table-per-Type], remove or refactor the owned type assignment. |
An owned property was configured as optional via .IsRequired(false) but the migration provider version does not honor the convention. | Column remains NOT NULL despite the API call. | Confirm your EF Core version supports the convention; run dotnet list package --include-transitive to verify the Microsoft.EntityFrameworkCore version. |
Ordered checks
- Confirm the API configuration. Locate the
.OwnsOne(e => e.Address)(or equivalent) call in yourDbContext. If the property should be optional, the call must include.IsRequired(false):OwnsOne(e => e.Address).IsRequired(false). If the property must be required, no additional flag is needed, but be aware that the column will be NOT NULL. - Inspect the generated migration. Open the most recent migration file and locate the column definition for the owned property. Look for
nullable: trueornullable: false. If the intent was optional and the migration showsfalse, proceed to the next step. - Apply the migration to a test database. Run
dotnet ef database update --context YourDbContextagainst a local SQL Server or SQLite instance. Required permissions: write access to the target database. Placeholder:YourDbContextreplaces your actual context name. Expected check: column nullability matches intent. Risk: applying migration to a production database alters schema; always use a test copy first. After the update, executeSELECT COLUMN_NAME, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = N'YourParentTable'to verify the nullability flag matches your intent. - Execute a LINQ query and capture SQL. Write a query such as
var result = await context.Parents.FirstAsync(p => p.Address.Street != null)and callcontext.Database.Log = Console.WriteLineto view the generated SQL. Confirm that the statement selects from the parent table only, without aJOIN, and that theWHEREclause references the owned columns directly.
Fixes tied to findings
- If step 1 revealed a missing
.IsRequired(false): Add the method call and re‑scaffold the migration withdotnet ef migrations add InitialCreate. The resulting column will be created asnullable. - If step 2 shows the migration ignored the convention: Verify you are using EF Core 5.0 or later, where the
.OwnsOne().IsRequired(false)convention is fully supported. Older versions may require the[Owned]attribute combined with optional configuration. - If step 3’s schema does not match: Re‑check for inheritance conflicts (see the diagnostic table). If the model mixes owned types with TPH/TPT, restructure the hierarchy so owned types reside exclusively on the base entity or a dedicated table, then re‑scaffold.
- If step 4’s SQL includes an unexpected
JOIN: This typically indicates the query optimizer or a custom query filter is pulling the owned data separately. Simplify the LINQ expression top => p.Address.Cityand ensure no.Includeor.ThenIncludeis driving the join.
Escalation criteria
If the migration still produces NOT NULL columns after adding .IsRequired(false), confirm that no ModelBuilder convention or global filter is overriding the per‑property setting. Use context.Model.GetEntityTypes().SelectMany(t => t.GetProperties()).Where(p => p.Name == "AddressLine1") to inspect the runtime configuration.
If concurrency surprises appear when rowversion is on the parent, consider marking the owned columns as timestamp‑free or adjusting the concurrency token to exclude embedded properties.
For models larger than a few entities where the pattern repeats, automate the verification: write a small utility that scaffolds a migration, parses the generated SQL, and asserts expected nullability. This catches regressions when the model evolves.
Practical verification checklist
- .OwnsOne() call includes
.IsRequired(false)for optional embedded objects. - Generated migration column shows
nullable: true. dotnet ef database updateon a test database reflects the expected nullability.- LINQ dot‑notation access produces SQL without a
JOIN. - No inheritance strategy conflict exists between owned types and TPH/TPT.
dotnet tool install --global dotnet-ef\n
dotnet ef migrations add InitialCreate --context YourDbContext\n# Inspect the generated .cs file for column nullability\n
dotnet ef database update --context YourDbContext\nSELECT COLUMN_NAME, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = N'Parents';\n# Enable SQL logging\ncontext.Database.Log = Console.WriteLine;\nvar _ = await context.Parents.FirstAsync();0 replies
A thoughtful contribution can make all the difference. Be the first to share one.