Skip to content

codingdroplets/efcore-10-whats-new

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EF Core 10 New Features Demo

Hands-on .NET 10 Web API showcasing the most impactful EF Core 10 features: LeftJoin/RightJoin LINQ operators, named query filters, ExecuteUpdateAsync delegate setters, and JSON column mapping — all in a single runnable project.

Visit CodingDroplets YouTube Patreon Buy Me a Coffee GitHub


🚀 Support the Channel — Join on Patreon

If this sample saved you time, consider joining our Patreon community. You'll get exclusive .NET tutorials, premium code samples, and early access to new content — all for the price of a coffee.

👉 Join CodingDroplets on Patreon

Prefer a one-time tip? Buy us a coffee ☕


📖 Read the Full Article

What's New in EF Core 10: Features Every .NET Developer Should Adopt in 2026

This project accompanies the article above. Reading it alongside the code will give you a deeper understanding of why each feature exists and when to use it.


🎯 What You'll Learn

  • LeftJoin / RightJoin LINQ operators — EF Core 10 promotes left/right joins to first-class LINQ citizens, replacing the verbose GroupJoin + DefaultIfEmpty pattern
  • Named query filters — Apply independent, named soft-delete filters per entity and selectively bypass individual filters with IgnoreQueryFilters()
  • ExecuteUpdateAsync delegate setters — Bulk-update rows in a single round-trip using strongly-typed property setters without loading entities into memory
  • JSON column mapping — Persist complex CLR types (List<string>) as JSON arrays in a single database column using value converters
  • ASP.NET Core Web API with .NET 10 — Controller-based API with built-in OpenAPI + Scalar UI, no Swashbuckle required

🗺️ Architecture Overview

HTTP Request
    │
    ▼
┌─────────────────────────────┐
│     ProductsController      │  ← ApiController (route: api/products)
│  GET  /                     │  → active products only (query filter)
│  GET  /left-join            │  → EF Core 10 LeftJoin operator
│  GET  /include-deleted      │  → IgnoreQueryFilters() bypass
│  PUT  /{id}/price-increase  │  → ExecuteUpdateAsync bulk update
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│        AppDbContext         │  ← EF Core 10 DbContext
│  Categories (soft-delete)   │
│  Products   (soft-delete    │
│             + JSON Tags)    │
└────────────┬────────────────┘
             │
             ▼
     SQLite  (app.db)

📋 EF Core 10 Feature Summary

Feature Class / Method What It Replaces
LeftJoin LINQ operator QueryableExtensions.LeftJoin() GroupJoin + DefaultIfEmpty
Named query filters HasQueryFilter(c => ...) Un-named global filters
Selective filter bypass IgnoreQueryFilters() IgnoreQueryFilters() (was all-or-nothing)
ExecuteUpdateAsync setters .ExecuteUpdateAsync(s => s.SetProperty(...)) Manual entity load + SaveChanges
JSON value converter HasConversion(serialize, deserialize) Separate join table or CSV string

📁 Project Structure

efcore-10-whats-new/
├── efcore-10-whats-new.sln
└── EFCore10Demo/
    ├── Controllers/
    │   └── ProductsController.cs   # All 4 demo endpoints
    ├── Data/
    │   ├── AppDbContext.cs         # EF Core DbContext with query filters + JSON mapping
    │   └── DatabaseSeeder.cs      # Seeds categories + products (incl. soft-deleted)
    ├── Models/
    │   ├── Category.cs             # Category entity with IsDeleted flag
    │   └── Product.cs              # Product entity with Tags (JSON column)
    ├── Properties/
    │   └── launchSettings.json    # Scalar UI auto-launch on run
    ├── appsettings.json            # SQLite connection string
    ├── appsettings.Development.json
    ├── EFCore10Demo.csproj
    └── Program.cs                  # Minimal hosting with OpenAPI + Scalar

🛠️ Prerequisites

Requirement Version
.NET SDK 10.0+
Any IDE Visual Studio 2022 v17.12+, Rider, or VS Code with C# Dev Kit
SQLite Bundled via Microsoft.EntityFrameworkCore.Sqlite — no install needed

⚡ Quick Start

# 1. Clone the repo
git clone https://github.com/codingdroplets/efcore-10-whats-new.git
cd efcore-10-whats-new

# 2. Build
dotnet build -c Release

# 3. Run (database is created and seeded automatically on first run)
cd EFCore10Demo
dotnet run

# 4. Open Scalar API UI
# Navigate to: http://localhost:5289/scalar/v1

The SQLite database (app.db) is created automatically on startup and seeded with sample categories and products — including some soft-deleted records so you can see the query filters in action.


🔧 How It Works

1. Soft-Delete Query Filters (EF Core 10)

EF Core 10 introduces named query filters. Each entity gets its own independently-bypassable filter:

// Data/AppDbContext.cs
modelBuilder.Entity<Category>().HasQueryFilter(c => !c.IsDeleted);
modelBuilder.Entity<Product>().HasQueryFilter(p => !p.IsDeleted);

Every LINQ query now automatically appends WHERE IsDeleted = 0 — you never accidentally expose deleted data.

2. LeftJoin LINQ Operator (EF Core 10)

Before EF Core 10, left joins required verbose GroupJoin + DefaultIfEmpty. Now it's a single operator:

// Controllers/ProductsController.cs — GetLeftJoin()
var result = await context.Categories
    .LeftJoin(
        context.Products,
        category => category.Id,
        product  => product.CategoryId,
        (category, product) => new
        {
            Category = category.Name,
            Product  = product != null ? product.Name : "— No Products —"
        })
    .ToListAsync();

The "Furniture" category is soft-deleted and therefore excluded by the query filter — perfect for demonstrating that both features compose cleanly.

3. Selective Filter Bypass

The include-deleted endpoint demonstrates bypassing query filters to return all records, including soft-deleted ones:

// Controllers/ProductsController.cs — GetIncludingDeleted()
var products = await context.Products
    .IgnoreQueryFilters()   // bypasses the soft-delete filter
    .Include(p => p.Category)
    .AsNoTracking()
    .ToListAsync();

4. ExecuteUpdateAsync — Bulk Update Without Loading Entities

EF Core 10 refines ExecuteUpdateAsync with delegate-based property setters. This issues a single UPDATE statement directly — no entity tracking, no round-trips:

// Controllers/ProductsController.cs — IncreasePriceByCategory()
var affected = await context.Products
    .Where(p => p.CategoryId == categoryId)
    .ExecuteUpdateAsync(setters => setters
        .SetProperty(p => p.Price, p => Math.Round(p.Price * 1.1m, 2)));

5. JSON Column Mapping

Product.Tags is a List<string> stored as a JSON array in a single TEXT column using a value converter:

// Data/AppDbContext.cs
modelBuilder.Entity<Product>()
    .Property(p => p.Tags)
    .HasConversion(
        v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
        v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions?)null) ?? new List<string>()
    );

This avoids a separate join table while keeping the data queryable and strongly-typed in C#.


📡 API Endpoints

Method Endpoint Description Status
GET /api/products Active products only (soft-delete filter auto-applied) 200
GET /api/products/left-join EF Core 10 LeftJoin — categories with their products 200
GET /api/products/include-deleted All products including soft-deleted (IgnoreQueryFilters) 200
PUT /api/products/category/{categoryId}/price-increase Bulk 10% price increase via ExecuteUpdateAsync 200 / 404

Scalar UI: Run the project and navigate to http://localhost:5289/scalar/v1 to explore and test all endpoints interactively.


🧪 Running Tests

This sample focuses on demonstrating EF Core 10 features via runnable API endpoints rather than unit tests. Use the Scalar API UI or any HTTP client to verify behaviour:

Endpoint Expected Behaviour
GET /api/products Returns 4 active products (Gaming Chair + Standing Desk excluded)
GET /api/products/left-join 2 active categories with their products listed
GET /api/products/include-deleted All 6 products including soft-deleted
PUT /api/products/category/1/price-increase Increases Electronics prices by 10%

🤔 Key Concepts

Why LeftJoin as a First-Class Operator?

Approach Code Complexity EF Core Version
GroupJoin + DefaultIfEmpty Verbose, 5+ lines EF Core < 10
Query syntax (join ... into grp) Readable but wordy All versions
LeftJoin() operator Clean, single method EF Core 10+

Query Filters vs Manual WHERE Clauses

Approach Risk of Forgetting Centralised
Manual WHERE IsDeleted = 0 in every query High No
EF Core Query Filters None (automatic) Yes

ExecuteUpdateAsync vs Load + SaveChanges

Approach SQL Round-Trips Memory Usage
Load entities + SaveChanges 2 (SELECT + UPDATE) Loads all rows
ExecuteUpdateAsync 1 (direct UPDATE) Zero — no entity tracking

🏷️ Technologies Used

  • .NET 10 — Target framework
  • ASP.NET Core Web API — Controller-based HTTP layer
  • EF Core 10.0.5 — ORM with new LINQ operators and filter improvements
  • SQLite — Lightweight embedded database (via Microsoft.EntityFrameworkCore.Sqlite)
  • Scalar.AspNetCore — Interactive API documentation UI (compatible with .NET 10 built-in OpenAPI)
  • Built-in ASP.NET Core OpenAPIAddOpenApi() + MapOpenApi() (no Swashbuckle needed)

📚 References


📄 License

This project is licensed under the MIT License.


🔗 Connect with CodingDroplets

Platform Link
🌐 Website https://codingdroplets.com/
📺 YouTube https://www.youtube.com/@CodingDroplets
🎁 Patreon https://www.patreon.com/CodingDroplets
☕ Buy Me a Coffee https://buymeacoffee.com/codingdroplets
💻 GitHub http://github.com/codingdroplets/

Want more samples like this? Support us on Patreon or buy us a coffee ☕ — every bit helps keep the content coming!

About

Hands-on demo of EF Core 10 new features: LeftJoin/RightJoin LINQ operators, named query filters, ExecuteUpdateAsync delegate setters, and JSON column mapping in ASP.NET Core Web API.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages