Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
EF Core 11.0 (EF11) is the next release after EF Core 10.0 and is currently in development.
Tip
You can run and debug into the samples by downloading the sample code from GitHub. Each section below links to the source code specific to that section.
EF11 requires the .NET 11 SDK to build and requires the .NET 11 runtime to run. EF11 will not run on earlier .NET versions, and will not run on .NET Framework.
Complex types
Complex types and JSON columns on entity types with TPT/TPC inheritance
EF Core has supported complex types and JSON columns for several versions, allowing you to model and persist nested, structured data within your entities. However, until now these features could not be used on entities with TPT (table-per-type) or TPC (table-per-concrete-type) inheritance.
Starting with EF 11, you can now use complex types and JSON columns on entity types with TPT and TPC inheritance mappings. For example, consider the following entity types with a TPT inheritance strategy:
public abstract class Animal
{
public int Id { get; set; }
public string Name { get; set; }
public required AnimalDetails Details { get; set; }
}
public class Dog : Animal
{
public string Breed { get; set; }
}
public class Cat : Animal
{
public bool IsIndoor { get; set; }
}
[ComplexType]
public class AnimalDetails
{
public DateTime BirthDate { get; set; }
public string? Veterinarian { get; set; }
}
With the following TPT mapping configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>().UseTptMappingStrategy();
}
EF 11 now properly supports this scenario, creating the Details_BirthDate and Details_Veterinarian columns on the Animal table for the complex type properties.
Similarly, JSON columns are now supported with TPT/TPC. The following configuration maps the Details property to a JSON column:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>()
.UseTptMappingStrategy()
.ComplexProperty(a => a.Details, b => b.ToJson());
}
This enhancement removes a significant limitation when modeling complex domain hierarchies, allowing you to combine the flexibility of TPT/TPC inheritance with the power of complex types and JSON columns.
For more information on inheritance mapping strategies, see Inheritance.
Complex types in Azure Cosmos DB
Complex types are now fully supported in the Azure Cosmos DB provider, embedded as nested JSON objects or arrays. For more information, see the Cosmos DB section below.
Stabilization and bug fixes
Significant effort has gone into making sure that complex type support is stable and bug-free, to unblock using complex types as an alternative to the owned entity mapping approach. Bugs fixed include:
- Error querying on complex type whose container is mapped to a table and a view
- Problem with ComplexProperty in EF9, when using the TPT approach
- Comparison of complex types does not compare properties within nested complex types
- Assignment of complex type does not assign nested properties correctly (ExecuteUpdate)
- Map two classes with same nullable complex properties to same column → NullReferenceException
- Complex property stored as JSON marked non-nullable in TPH class hierarchy
- EntityEntry.ReloadAsync throws when nullable complex property is null
- Unnecessary columns in SQL with Complex Types + object closures in projections
LINQ and SQL translation
Better SQL for to-one joins
EF Core 11 generates better SQL when querying with to-one (reference) navigation includes in two ways.
First, when using split queries (AsSplitQuery()), EF previously added unnecessary joins to reference navigations in the SQL generated for collection queries. For example, consider the following query:
var blogs = context.Blogs
.Include(b => b.BlogType)
.Include(b => b.Posts)
.AsSplitQuery()
.ToList();
EF Core previously generated a split query for Posts that unnecessarily joined BlogType:
-- Before EF Core 11
SELECT [p].[Id], [p].[BlogId], [p].[Title], [b].[Id], [b0].[Id]
FROM [Blogs] AS [b]
INNER JOIN [BlogType] AS [b0] ON [b].[BlogTypeId] = [b0].[Id]
INNER JOIN [Post] AS [p] ON [b].[Id] = [p].[BlogId]
ORDER BY [b].[Id], [b0].[Id]
In EF Core 11, the unneeded join is pruned:
-- EF Core 11
SELECT [p].[Id], [p].[BlogId], [p].[Title], [b].[Id]
FROM [Blogs] AS [b]
INNER JOIN [Post] AS [p] ON [b].[Id] = [p].[BlogId]
ORDER BY [b].[Id]
Second, EF no longer adds redundant keys from reference navigations to ORDER BY clauses. Because a reference navigation's key is functionally determined by the parent entity's key (via the foreign key), it does not need to appear separately. For example:
var blogs = context.Blogs
.Include(b => b.Owner)
.Include(b => b.Posts)
.ToList();
EF Core previously included [p].[PersonId] in the ORDER BY, even though [b].[BlogId] already uniquely identifies the row:
-- Before EF Core 11
ORDER BY [b].[BlogId], [p].[PersonId]
In EF Core 11, the redundant column is omitted:
-- EF Core 11
ORDER BY [b].[BlogId]
Both optimizations can have a significant positive impact on query performance, especially when multiple reference navigations are included. A simple, common split query scenario showed a 29% improvement in querying performance, as the database no longer has to perform the to-one join; single queries are also significantly improved by the removal of the ORDER BY, even if a bit less: one scenario showed a 22% improvement.
More details on the benchmark are available here, and as always, actual performance in your application will vary based on your schema, data and a variety of other factors.
MaxBy and MinBy
EF Core now supports translating the LINQ MaxByAsync and MinByAsync methods (and their sync counterparts). These methods allow you to find the element with the maximum or minimum value for a given key selector, rather than just the maximum or minimum value itself.
For example, to find the blog with the most posts:
var blogWithMostPosts = await context.Blogs.MaxByAsync(b => b.Posts.Count());
This translates to the following SQL:
SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
ORDER BY (
SELECT COUNT(*)
FROM [Posts] AS [p]
WHERE [b].[Id] = [p].[BlogId]) DESC
Similarly, MinByAsync orders ascending and returns the element with the minimum value for the key selector.
EF.Functions.JsonPathExists()
EF Core 11 introduces EF.Functions.JsonPathExists(), which checks whether a given JSON path exists in a JSON document. On SQL Server, this translates to the JSON_PATH_EXISTS function (available since SQL Server 2022).
The following query filters blogs to those whose JSON data contains an OptionalInt property:
var blogs = await context.Blogs
.Where(b => EF.Functions.JsonPathExists(b.JsonData, "$.OptionalInt"))
.ToListAsync();
This generates the following SQL:
SELECT [b].[Id], [b].[Name], [b].[JsonData]
FROM [Blogs] AS [b]
WHERE JSON_PATH_EXISTS([b].[JsonData], N'$.OptionalInt') = 1
EF.Functions.JsonPathExists() accepts a JSON value and a JSON path to check for. It can be used with scalar string properties, complex types, and owned entity types mapped to JSON columns.
For the full JSON_PATH_EXISTS SQL Server documentation, see JSON_PATH_EXISTS.
SQL Server
VECTOR_SEARCH() and vector indexes
Warning
VECTOR_SEARCH() and vector indexes are currently experimental features in SQL Server and are subject to change. The APIs in EF Core for these features are also subject to change.
In EF Core 10, we introduced translation for EF.Functions.VectorDistance(), which is a scalar function that computes the distance between two vectors. This function can be used in LINQ queries for vector similarity search, allowing you to find the most similar embeddings to a given embedding. However, VectorDistance() computes an exact distance between the given vectors.
When querying large datasets, SQL Server 2025 also supports performing approximate search over a vector index, which provides much better performance at the expense of returning items that are approximately similar - rather than exactly similar - to the query. EF 11 now supports creating vector indexes through migrations:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasVectorIndex(b => b.Embedding, "cosine");
}
Once you have a vector index, you can use the VectorSearch() extension method on your DbSet to perform an approximate search:
var blogs = await context.Blogs
.VectorSearch(b => b.Embedding, embedding, "cosine", topN: 5)
.ToListAsync();
This translates to the SQL Server VECTOR_SEARCH() table-valued function, which performs an approximate search over the vector index. The topN parameter specifies the number of results to return.
VectorSearch() returns VectorSearchResult<TEntity>, allowing you to access the distance alongside the entity.
For more information, see the full documentation on vector search.
Vector properties not loaded by default
EF Core 11 changes how vector properties are loaded: SqlVector<T> columns are no longer included in SELECT statements when materializing entities. Since vectors can be quite large—containing hundreds or thousands of floating-point numbers—this avoids unnecessary data transfer in the common case where vectors are ingested and used for search but not read back.
// Vector column is excluded from the projected entity
var blogs = await context.Blogs.OrderBy(b => b.Name).ToListAsync();
// Generates: SELECT [b].[Id], [b].[Name] FROM [Blogs] AS [b] ...
// Explicit projection still loads the vector
var embeddings = await context.Blogs
.Select(b => new { b.Id, b.Embedding })
.ToListAsync();
Vector properties can still be used in WHERE and ORDER BY clauses—including with VectorDistance() and VectorSearch()—and EF will correctly include them in the SQL, just not in the entity projection.
This optimization can have a dramatic impact on application speed: a minimal benchmark showed an almost 9x (that's nine-fold) increase in performance, and that's while running against a local database. The optimization is even more impactful as latency to the database grows: when executing against a remote Azure SQL database, an improvement of around 22x was observed. More details on the benchmark are available here, and as always, actual performance in your application will vary based on your entity, vector properties and latency.
Full-text search improvements
Full-text search catalog and index creation
SQL Server's full-text search requires a full-text catalog and index to be set up on your database before you can use it. EF 11 now allows configuring full-text catalogs and indexes in your model, so that EF migrations can automatically create and manage them for you:
// In your OnModelCreating:
modelBuilder.HasFullTextCatalog("ftCatalog");
modelBuilder.Entity<Blog>()
.HasFullTextIndex(b => b.FullName)
.HasKeyIndex("PK_Blogs")
.OnCatalog("ftCatalog");
This generates the following SQL in a migration:
CREATE FULLTEXT CATALOG [ftCatalog];
CREATE FULLTEXT INDEX ON [Blogs]([FullName]) KEY INDEX [PK_Blogs] ON [ftCatalog];
Previously, full-text catalog and index creation had to be managed manually by adding SQL to migrations. For full details on setting up full-text catalogs and indexes, see the full-text search documentation.
Full-text search table-valued functions
EF Core has long provided support for SQL Server's full-text search predicates FREETEXT() and CONTAINS(), via EF.Functions.FreeText() and EF.Functions.Contains(). These predicates can be used in LINQ Where() clauses to filter results based on search criteria.
However, SQL Server also has table-valued function versions of these functions, FREETEXTTABLE() and CONTAINSTABLE(), which also return a ranking score along with the results, providing additional flexibility over the predicate versions. EF 11 now supports these table-valued functions:
// Using FreeTextTable with a search query
var results = await context.Blogs
.FreeTextTable(b => b.FullName, "John")
.Select(r => new { Blog = r.Value, Rank = r.Rank })
.OrderByDescending(r => r.Rank)
.ToListAsync();
// Using ContainsTable with a search query
var results = await context.Blogs
.ContainsTable(b => b.FullName, "John")
.Select(r => new { Blog = r.Value, Rank = r.Rank })
.OrderByDescending(r => r.Rank)
.ToListAsync();
Both methods return FullTextSearchResult<TEntity>, giving you access to both the entity and the ranking value from SQL Server's full-text engine. This allows for more sophisticated result ordering and filtering based on relevance scores.
For more information, see the full documentation on full-text search.
Contains operations using JSON_CONTAINS
SQL Server 2025 introduced the JSON_CONTAINS function, which checks whether a value exists in a JSON document. Starting with EF Core 11, when targeting SQL Server 2025, LINQ Contains queries over primitive (or scalar) collections stored as JSON are translated to use this function, replacing the previous, less efficient OPENJSON-based translation.
The following query checks whether a blog's Tags collection contains a specific tag:
var blogs = await context.Blogs
.Where(b => b.Tags.Contains("ef-core"))
.ToListAsync();
Before EF 11 - or when targeting older SQL Server versions - this generates the following SQL:
SELECT [b].[Id], [b].[Name], [b].[Tags]
FROM [Blogs] AS [b]
WHERE N'ef-core' IN (
SELECT [t].[value]
FROM OPENJSON([b].[Tags]) WITH ([value] nvarchar(max) '$') AS [t]
)
With EF 11, configure EF to target SQL Server 2025 by setting the compatibility level as follows:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer("<CONNECTION STRING>", o => o.UseCompatibilityLevel(170));
At that point, EF will instead generate the following SQL:
SELECT [b].[Id], [b].[Name], [b].[Tags]
FROM [Blogs] AS [b]
WHERE JSON_CONTAINS([b].[Tags], 'ef-core') = 1
JSON_CONTAINS() can notably make use of a JSON index, if one is defined.
Note
JSON_CONTAINS does not support searching for null values. As a result, this translation is only applied when EF can determine that at least one side is non-nullable — either the item being searched for (e.g. a non-null constant or a non-nullable column), or the collection's elements. When this cannot be determined, EF falls back to the previous OPENJSON-based translation.
EF.Functions.JsonContains()
In the section above, EF Core automatically translates LINQ Contains queries over primitive collections to use the SQL Server JSON_CONTAINS function. In some cases, however, you may want to directly invoke JSON_CONTAINS yourself, for example to search for a value at a specific path, or to specify a search mode. For these cases, EF Core 11 introduces EF.Functions.JsonContains().
The following query checks whether a blog's JSON data contains a specific value at a given path:
var blogs = await context.Blogs
.Where(b => EF.Functions.JsonContains(b.JsonData, 8, "$.Rating") == 1)
.ToListAsync();
This generates the following SQL:
SELECT [b].[Id], [b].[Name], [b].[JsonData]
FROM [Blogs] AS [b]
WHERE JSON_CONTAINS([b].[JsonData], 8, N'$.Rating') = 1
EF.Functions.JsonContains() accepts the JSON value to search in, the value to search for, and optionally a JSON path and a search mode. It can be used with scalar string properties, complex types, and owned entity types mapped to JSON columns.
For the full JSON_CONTAINS SQL Server documentation, see JSON_CONTAINS.
Cosmos DB
Complex types
EF Core complex types are now fully supported in the Azure Cosmos DB provider; they are embedded as nested JSON objects (or arrays, for collections) within the owning document, with support for queries, inserts, and updates.
For example, the following configures ShippingAddress as a complex type:
modelBuilder.Entity<Order>()
.ComplexProperty(o => o.ShippingAddress);
Complex types are generally a better fit than owned types when mapping to JSON documents: unlike owned types, complex types have value semantics and no identity, which means they work better in scenarios such as comparing two embedded objects within the same document.
This feature was contributed by @JoasE - many thanks!
Transactional batches
Azure Cosmos DB supports transactional batches, which allow multiple operations to be executed atomically and in a single roundtrip against a single partition. Starting with EF Core 11, the provider leverages transactional batches by default, providing best-effort atomicity and improved performance when saving changes.
The batching behavior can be controlled via the AutoTransactionBehavior property:
- Auto (default): Operations are grouped into transactional batches by container and partition. Batches are executed sequentially; if a batch fails, subsequent batches are not executed.
- Never: All operations are performed individually and sequentially (the pre-11 behavior).
- Always: Requires all operations to fit in a single transactional batch; throws if they cannot.
For more information, see the documentation.
This feature was contributed by @JoasE - many thanks!
Bulk execution
Azure Cosmos DB supports bulk execution, which allows multiple document operations to be executed in parallel and across DbContext instances, significantly improving throughput when saving many entities at once. EF Core now supports enabling bulk execution:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseCosmos(
"<connection string>",
databaseName: "OrdersDB",
options => options.BulkExecutionEnabled());
For more information, see the documentation.
This feature was contributed by @JoasE - many thanks!
Session token management
Azure Cosmos DB uses session tokens to track read-your-writes consistency within a session. When running in an environment with multiple instances (e.g., with round-robin load balancing), you may need to manually manage session tokens to ensure consistency across requests.
EF Core now provides APIs to retrieve and set session tokens on a DbContext. To enable manual session token management, configure the SessionTokenManagementMode():
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseCosmos(
"<connection string>",
databaseName: "OrdersDB",
options => options.SessionTokenManagementMode(SessionTokenManagementMode.SemiAutomatic));
You can then retrieve and use session tokens:
// After performing operations, retrieve the session token
var sessionToken = context.Database.GetSessionToken();
// Later, in a different context instance, apply the session token before reading
context.Database.UseSessionToken(sessionToken);
var result = await context.Documents.FindAsync(id);
For more information, see the documentation.
This feature was contributed by @JoasE - many thanks!
Migrations
Excluding foreign key constraints from migrations
It is now possible to configure a foreign key relationship in the EF model while preventing the corresponding database constraint from being created by migrations. This is useful for legacy databases without existing constraints, or in data synchronization scenarios where referential integrity constraints might conflict with the synchronization order:
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.ExcludeForeignKeyFromMigrations();
The relationship is fully supported in EF for queries, change tracking, etc. Only the foreign key constraint in the database is suppressed; a database index is still created on the foreign key column.
For more information, see Excluding foreign key constraints from migrations.
Latest migration ID recorded in model snapshot
When working in team environments, it's common for multiple developers to create migrations on separate branches. When these branches are merged, the migration trees can diverge, leading to issues that are sometimes difficult to detect.
Starting with EF Core 11, the model snapshot now records the ID of the latest migration. When two developers create migrations on divergent branches, both branches will modify this value in the model snapshot, causing a source control merge conflict. This conflict alerts the team that they need to resolve the divergence - typically by discarding one of the migration trees and creating a new, unified migration.
For more information on managing migrations in team environments, see Migrations in Team Environments.
Create and apply migrations in one step
The dotnet ef database update command now supports creating and applying a migration in a single step using the new --add option. This uses Roslyn to compile the migration at runtime, enabling scenarios like .NET Aspire and containerized applications where recompilation isn't possible:
dotnet ef database update InitialCreate --add
This command scaffolds a new migration named InitialCreate, compiles it using Roslyn, and immediately applies it to the database. The migration files are still saved to disk for source control and future recompilation. The same options available for dotnet ef migrations add can be used:
dotnet ef database update AddProducts --add --output-dir Migrations/Products --namespace MyApp.Migrations
In PowerShell, use the -Add parameter:
Update-Database -Migration InitialCreate -Add
Connection and offline options for migrations remove
The dotnet ef migrations remove and database drop commands now accept --connection parameters, allowing you to specify the database connection string directly without needing to configure a default connection in your DbContext. Additionally, migrations remove supports the new --offline option to remove a migration without connecting to the database:
# Remove migration with specific connection
dotnet ef migrations remove --connection "Server=prod;Database=MyDb;..."
# Remove migration without connecting to database (offline mode)
dotnet ef migrations remove --offline
# Revert and remove applied migration
dotnet ef migrations remove --force
# Drop specific database by connection string
dotnet ef database drop --connection "Server=test;Database=MyDb;..." --force
The --offline option skips the database connection check entirely, which is useful when the database is inaccessible or when you're certain the migration hasn't been applied. Note that --offline and --force cannot be used together, since --force requires a database connection to check if the migration has been applied before reverting it.
In PowerShell, use the -Connection and -Offline parameters:
Remove-Migration -Connection "Server=prod;Database=MyDb;..."
Remove-Migration -Offline
Drop-Database -Connection "Server=test;Database=MyDb;..." -Force
Other improvements
- The EF command-line tool now writes all logging and status messages to standard error, reserving standard output only for the command's actual expected output. For example, when generating a migration SQL script with
dotnet ef migrations script, only the SQL is written to standard output.