Remigiusz ZalewskiRemigiusz Zalewski

EF Core 10 Is INSANE - Overview of Game Changer features

ef-core-10-new-features

Introduction

Most EF Core releases are a bag of small quality-of-life fixes. EF Core 10 has a few of those too, but it also lands several changes that remove long-standing workarounds: you no longer hand-write a GroupJoin plus SelectMany plus DefaultIfEmpty to get a left join, you no longer have one all-or-nothing global query filter, and you can now do vector similarity search against SQL Server without a bolt-on.

This video runs through the headline features against a small blog/book/author model on .NET 10 and SQL Server. Here is what each one is and when you would reach for it.

🎬 Watch the full video here:


LeftJoin and RightJoin as real operators

Before EF Core 10, a left join in LINQ-to-Entities was the incantation everyone had to look up: GroupJoin, then SelectMany, then DefaultIfEmpty. It worked, it translated fine, and nobody remembered the shape.

EF Core 10 adds LeftJoin and RightJoin as first-class query operators:

await context.Authors
    .LeftJoin(context.Books,
        author => author.Id,
        book => book.AuthorId,
        (author, book) => new { author.Name, book.Title })
    .ToListAsync();

Same generated SQL as the old pattern, a fraction of the code, and it reads like the join you meant. This is the change you will use most often.

Named query filters

Global query filters (soft delete, multi-tenancy, "only verified rows") have always had one painful limitation: one filter per entity, and IgnoreQueryFilters() turned off all of them. If you had soft-delete and tenant filtering on the same entity, you could not disable one without disabling the other.

EF Core 10 lets you name filters and toggle them individually:

modelBuilder.Entity<Author>()
    .HasQueryFilter("SoftDeletionFilter", a => !a.IsDeleted)
    .HasQueryFilter("VerifiedFilter", a => a.IsVerified);
// disable just one, keep the rest
await context.Authors
    .IgnoreQueryFilters(["SoftDeletionFilter"])
    .ToListAsync();

An admin screen that needs to see soft-deleted rows but must still respect tenant isolation is now a one-liner instead of a manual re-implementation of the other filter.

Complex types mapped to JSON

EF Core 10 lets a complex type be stored as a JSON document in a column:

modelBuilder.Entity<Book>()
    .ComplexProperty(x => x.Attributes, x => x.ToJson());

BookAttributes (genre, page count, language, a string[] of tags) lives in one nvarchar column as JSON, and you can still query into it in LINQ:

await context.Books
    .Where(b => b.Attributes.Pages > 300)
    .ToListAsync();

EF Core translates the predicate to SQL JSON path access. This is the pragmatic middle ground between a rigid column-per-field schema and an untyped blob - useful for attribute bags that vary and are not worth their own table.

Vector search for RAG, built in

This is the flashiest one. SQL Server 2025 has a native vector type, and EF Core 10 can map to it and translate distance functions:

[Column(TypeName = "vector(1536)")]
public SqlVector<float> Vector { get; set; }
await context.BlogPosts
    .OrderBy(p => EF.Functions.VectorDistance("cosine", p.Vector, queryVector))
    .Take(3)
    .ToListAsync();

Combined with Microsoft.Extensions.AI to generate embeddings (the demo uses an Azure OpenAI embedding model), you get the retrieval half of a RAG pipeline - "find the three most semantically similar posts to this text" - as an ordinary EF Core query, with no separate vector database.

Also worth knowing

  • ExecuteUpdateAsync continues to expand what it can express, including updates driven by other columns, so more bulk updates avoid loading entities.
  • Various LINQ translation gaps closed, and better SQL for existing queries - the kind of thing you get for free by upgrading.

Common pitfalls

  • Vector search needs the right SQL Server. The native vector type is SQL Server 2025 / recent Azure SQL. Older targets do not have it.
  • Named filters are opt-in by name. If you migrate existing single filters, give them names and update every IgnoreQueryFilters() call site, or behavior shifts.
  • JSON-mapped complex types query well but index differently. Filtering b.Attributes.Pages > 300 works, but performance depends on JSON indexing support in your database - do not assume it is free on a large table.
  • LeftJoin result selectors can yield nulls. The right-side entity is null when there is no match; project defensively.

Key Takeaways

  • LeftJoin / RightJoin are now first-class operators - retire the GroupJoin + SelectMany + DefaultIfEmpty pattern.
  • Query filters can be named and disabled individually with IgnoreQueryFilters(["Name"]), fixing the all-or-nothing problem for soft delete plus multi-tenancy.
  • Complex types can be stored as JSON in a single column and still be queried in LINQ.
  • SQL Server's native vector type plus EF.Functions.VectorDistance gives you similarity search - the retrieval side of RAG - without a dedicated vector store.
  • Upgrading also brings a batch of translation improvements you do not have to ask for.

Get the Full Source Code

The complete runnable solution - the blog/book/author model, the named query filters, the JSON complex type, and the vector-search endpoints wired to an embedding generator - is available to Patreon supporters. If you want to run the RAG query against real embeddings instead of rebuilding it from the walkthrough above, you can find it on Patreon.

Resources