June 21, 2026
EF Core 10: what's new, and how to let an AI agent write safe migrations
A complete look at EF Core 10 new features - LeftJoin/RightJoin, named query filters, vector search, JSON columns - plus how to let Claude Code write safe migrations.
EF Core 10 is an LTS release that shipped in November 2025, supported until November 10, 2028. It runs on .NET 10 only. This post covers what is actually new (verified against the Microsoft Learn “What’s New in EF Core 10” page) and then the practical question: how do you let an AI agent generate EF Core migrations without breaking things?
What’s new in EF Core 10?
LeftJoin and RightJoin LINQ operators
EF Core 10 supports the new LeftJoin and RightJoin methods that .NET 10 adds to LINQ. Before EF 10, a left outer join required an awkward combination of SelectMany, GroupJoin, and DefaultIfEmpty. The new operators make it direct:
var query = context.Students
.LeftJoin(
context.Departments,
student => student.DepartmentID,
department => department.ID,
(student, department) => new
{
student.FirstName,
Department = department.Name ?? "[NONE]"
});
EF 10 translates both operators to LEFT JOIN and RIGHT JOIN in SQL. C# query syntax does not yet support these operators; use method syntax.
Named query filters
Named query filters let you attach multiple global filters to an entity type and disable individual ones per query. Before EF 10, each entity type supported only one global filter, and IgnoreQueryFilters() disabled all of them. Now:
modelBuilder.Entity<Blog>()
.HasQueryFilter("SoftDeletionFilter", b => !b.IsDeleted)
.HasQueryFilter("TenantFilter", b => b.TenantId == tenantId);
// Disable only the soft-deletion filter for this query:
var allBlogs = await context.Blogs
.IgnoreQueryFilters(["SoftDeletionFilter"])
.ToListAsync();
This makes multi-tenant + soft-delete patterns significantly cleaner - no more choosing between one combined filter and no filtering at all.
Native JSON column support (SQL Server 2025 / Azure SQL)
EF Core 10 automatically uses the new json data type on SQL Server 2025 or Azure SQL when you use UseAzureSql() or set a compatibility level of 170 or higher. Previously, JSON data was stored in nvarchar(max) columns. The new type brings efficiency improvements and a safer storage mechanism. Primitive collections and complex types mapped to JSON use the new type automatically. Existing nvarchar(max) JSON columns are migrated automatically on the next migration.
EF 10 also adds ExecuteUpdateAsync support for JSON columns, enabling efficient bulk updates:
await context.Blogs.ExecuteUpdateAsync(s =>
s.SetProperty(b => b.Details.Views, b => b.Details.Views + 1));
Note: ExecuteUpdateAsync for JSON requires mapping your types as complex types, not owned entities.
Vector similarity search - out of preview
Vector search, introduced experimentally in EF 9, is production-ready in EF 10. The feature targets Azure SQL and SQL Server 2025. You store embeddings in a SqlVector<float> column and query with EF.Functions.VectorDistance():
var topSimilarBlogs = context.Blogs
.OrderBy(b => EF.Functions.VectorDistance("cosine", b.Embedding, queryVector))
.Take(3)
.ToListAsync();
The model-building API was also renamed: use IsVectorProperty() and IsVectorIndex() (replacing the preview names). This is the foundation for semantic search and RAG workloads directly in SQL Server.
Complex types: table splitting and JSON mapping
EF 10 solidifies complex types as the recommended pattern for modeling value objects that map to either additional columns (table splitting) or a JSON column. Unlike owned entity types, complex types have value semantics: you can assign one to another and compare them by contents, both of which fail with owned entities. Complex types now also support optional properties and .NET structs.
Parameterized collection translation improvements
EF 10 changes the default translation of parameterized collections (ids.Contains(b.Id) style queries) to use individual scalar parameters instead of a JSON array. This gives the query planner cardinality information while keeping the SQL shape stable across invocations, reducing plan cache misses. EF also pads parameter lists to reduce the number of distinct SQL shapes.
Other notable changes
ExecuteUpdateAsyncnow accepts a regular lambda (not just an expression tree), making conditional bulk updates dramatically simpler to write.- SQL injection warnings: a new Roslyn analyzer warns when string concatenation is used inside raw SQL methods like
FromSqlRaw. - Inlined constants in SQL are now redacted from logs by default.
- Split query ordering is now consistent, fixing a long-standing data integrity edge case.
How do I let Claude Code generate EF Core 10 migrations without breaking things?
The rule is simple: Claude Code runs dotnet ef tooling; it never hand-edits the generated files. Here is how to wire this up reliably.
Step 1 - Document the exact migration command in CLAUDE.md
In your CLAUDE.md, include the exact command the agent should run when you ask it to add a migration:
## EF Core commands
Add migration: dotnet ef migrations add <Name> --project src/Infrastructure --startup-project src/Api
Apply to dev DB: dotnet ef database update --project src/Infrastructure --startup-project src/Api
Generate SQL script: dotnet ef migrations script --project src/Infrastructure --startup-project src/Api
Also add an explicit constraint:
## EF Core rules
- NEVER hand-edit *.Designer.cs or *ModelSnapshot.cs files.
Only dotnet ef migrations commands write to those files.
- Review the Up() and Down() methods in every generated migration before committing.
Step 2 - Pre-approve the ef commands in .claude/settings.json
Without pre-approval, Claude Code will pause and ask permission every time it runs dotnet ef. Add it to the allowed list:
{
"permissions": {
"allow": [
"Bash(dotnet build *)",
"Bash(dotnet test *)",
"Bash(dotnet format *)",
"Bash(dotnet ef migrations add *)",
"Bash(dotnet ef database update *)",
"Bash(dotnet ef migrations script *)"
]
}
}
This is the same pattern used in the github.com/Khavel/dotnet-claude-starter starter repo.
Step 3 - Tell the agent what to do after generating the migration
In your task prompt (or in CLAUDE.md under “Definition of done”), include:
After adding a migration: run
dotnet ef migrations scriptto generate SQL. Inspect the Up() and Down() methods and confirm they match the intent. Rundotnet buildanddotnet testto confirm nothing is broken.
The agent will generate the SQL and include it in its summary, giving you a reviewable artifact before anything hits a real database.
How do I verify a migration the agent wrote?
Review the Up() and Down() methods in the migration file directly. This is the most important step. The *.Designer.cs and *ModelSnapshot.cs files are generated - you do not need to read them. What you need to read is the migration class itself:
- Does
Up()do exactly what the model change requires, no more? - Does
Down()correctly undo theUp()change? - If the migration drops a column, does
Down()re-add it with the right type and constraints? - For JSON column migrations on SQL Server 2025: will this change
nvarchar(max)columns tojson? Is that intended for every environment?
Then run dotnet ef migrations script and inspect the generated SQL. Apply it to a dev database and verify the schema with your database client.
FAQ
What are the most important new features in EF Core 10?
EF Core 10 ships LeftJoin and RightJoin LINQ operators, named query filters, full native JSON column support for SQL Server 2025/Azure SQL, vector similarity search out of preview, complex type table-splitting and JSON mapping, and improved parameterized collection translation.
Can Claude Code generate EF Core migrations automatically?
Yes. Give Claude Code the exact dotnet ef migrations add command in CLAUDE.md, pre-approve it in .claude/settings.json, and instruct it never to hand-edit the Designer.cs or ModelSnapshot.cs files. The agent runs the tooling; the tooling generates the migration.
Why should I never hand-edit EF Core migration Designer.cs or ModelSnapshot.cs files?
These files are generated by the dotnet ef tooling. Hand-editing them breaks the migration chain and can cause subsequent migrations to be incorrect or fail to apply. The rule applies to both humans and AI agents.
How do I verify a migration that an AI agent wrote?
Review the Up() and Down() methods in the migration file. Run dotnet ef migrations script to generate the SQL. Apply to a dev database and verify the schema before committing.
Does the dotnet-claude-starter include EF Core?
The starter uses in-memory storage. EF Core with PostgreSQL is what the production Sharpyard kit adds. The starter’s CLAUDE.md and settings.json patterns are the foundation you carry forward when you swap to a real database.
Building a production .NET SaaS, agent-first?
The free starter at github.com/Khavel/dotnet-claude-starter gives you the agent operating layer on a .NET 10 minimal API. Sharpyard is the production kit: .NET 10, Angular, auth, multi-tenancy, billing, EF Core + PostgreSQL, and the full agent layer (CLAUDE.md, MCP config, contract tests, pre-approved commands) wired throughout. Join the founding waitlist for early access and the founding price.
Related: Claude Code for .NET developers.
The full AI-native .NET SaaS starter kit.
Join the waitlist