Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .github/workflows/build-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ on:
- "dotnet-8-essentials/configuration-secrets-environments/**"
- "dotnet-8-essentials/core-features-get-started/**"
- "dotnet-8-essentials/observability-opentelemetry/**"
- "ef-core/advanced-modeling-performance/**"
- ".github/workflows/build-samples.yml"

pull_request:
Expand Down Expand Up @@ -54,6 +55,7 @@ on:
- "dotnet-8-essentials/configuration-secrets-environments/**"
- "dotnet-8-essentials/core-features-get-started/**"
- "dotnet-8-essentials/observability-opentelemetry/**"
- "ef-core/advanced-modeling-performance/**"
- ".github/workflows/build-samples.yml"

workflow_dispatch:
Expand Down Expand Up @@ -1561,3 +1563,107 @@ jobs:
grep --fixed-strings --quiet 'Checkout.Process' "${app_log}"
grep --fixed-strings --quiet 'checkout.requests' "${app_log}"
grep --fixed-strings --quiet 'Checkout accepted' "${app_log}"

test-ef-core-hot-path:
name: Test EF Core hot-path sample
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Check out repository
uses: actions/checkout@v5

- name: Install .NET 10 SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.0.x"

- name: Restore
run: >
dotnet restore
ef-core/advanced-modeling-performance/EfCoreHotPathMinimal.slnx

- name: Build
run: >
dotnet build
ef-core/advanced-modeling-performance/EfCoreHotPathMinimal.slnx
--configuration Release
--no-restore

- name: Test
run: >
dotnet test
ef-core/advanced-modeling-performance/EfCoreHotPathMinimal.slnx
--configuration Release
--no-build

- name: Verify EF Core package baseline
shell: bash
run: |
project="ef-core/advanced-modeling-performance/src/EfCoreHotPathMinimal/EfCoreHotPathMinimal.csproj"

test "$(
grep \
--count \
'<PackageReference' \
"${project}"
)" = "1"

grep \
--fixed-strings \
'Include="Microsoft.EntityFrameworkCore.Sqlite"' \
"${project}"

grep \
--fixed-strings \
'Version="10.0.11"' \
"${project}"

- name: Verify exact sample output
shell: bash
run: |
output="$(
dotnet run \
--project ef-core/advanced-modeling-performance/src/EfCoreHotPathMinimal/EfCoreHotPathMinimal.csproj \
--configuration Release \
--no-build
)"

expected="$(
printf '%s\n' \
'EF Core Hot-Path Query Lab' \
'Visible products: 4' \
'Compiled lookup: SKU-003 | Dock | 12900 cents' \
'Restocked products: 3' \
'Purged soft-deleted products: 1' \
'Final rows: 4'
)"

printf '%s\n' "${output}"

if [[ "${output}" != "${expected}" ]]; then
echo "Unexpected deterministic sample output."
exit 1
fi

- name: Verify no generated database artifacts
shell: bash
run: |
if find \
ef-core/advanced-modeling-performance \
-type f \
\( \
-name '*.db' \
-o -name '*.sqlite' \
-o -name '*.sqlite3' \
\) \
-print \
-quit |
grep \
--quiet \
.; then
echo "Generated SQLite database file found in sample tree."
exit 1
fi
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Each sample folder contains a focused implementation of one tutorial topic. The
| [`dotnet-8-essentials/configuration-secrets-environments`](dotnet-8-essentials/configuration-secrets-environments/) | Focused .NET 10 Minimal API demonstrating layered configuration precedence, prefixed environment variables, command-line overrides, strongly typed options, startup validation, User Secrets metadata, a lightweight feature flag, and safe Development-only diagnostics | [.NET 8 Configuration & Secrets Management: Typed Options, User Secrets & Feature Flags](https://www.dotnet-guide.com/tutorials/dotnet-8-essentials/configuration-secrets-environments/) |
| [`dotnet-8-essentials/core-features-get-started`](dotnet-8-essentials/core-features-get-started/) | Focused .NET 10 Native AOT Minimal API demonstrating CreateSlimBuilder, source-generated JSON, typed DI, AOT-safe endpoints, analyzer-aware publishing, and direct native-binary smoke testing | [.NET 8 Essentials: Core Features & Getting Started](https://www.dotnet-guide.com/tutorials/dotnet-8-essentials/core-features-get-started/) |
| [`dotnet-8-essentials/observability-opentelemetry`](dotnet-8-essentials/observability-opentelemetry/) | Focused .NET 10 OpenTelemetry companion demonstrating ASP.NET Core request instrumentation, custom ActivitySource spans, low-cardinality Meter metrics, structured ILogger events, automatic log-to-trace correlation, console export, and deterministic tests without external observability infrastructure | [.NET 8 Observability with OpenTelemetry: Tracing, Metrics & Structured Logging](https://www.dotnet-guide.com/tutorials/dotnet-8-essentials/observability-opentelemetry/) |
| [`ef-core/advanced-modeling-performance`](ef-core/advanced-modeling-performance/) | Focused .NET 10 / EF Core 10 relational-data companion demonstrating DTO projection, no-tracking reads, a compiled hot-path query, named soft-delete filtering, ExecuteUpdate, ExecuteDelete, and explicit change-tracker caveats with deterministic SQLite tests | [EF Core Advanced Modeling & Performance: Owned Types, Converters, JSON/Temporal Tables, Compiled Queries](https://www.dotnet-guide.com/tutorials/ef-core/advanced-modeling-performance/) |

## Companion articles
- [Common Microsoft.Extensions.AI mistakes](https://www.dotnet-guide.com/articles/dotnet-ai/microsoft-extensions-ai-common-mistakes/)
Expand Down Expand Up @@ -469,6 +470,28 @@ tutorials/
| `-- MinimalApiPipeline.Tests/
| |-- MinimalApiPipeline.Tests.csproj
| `-- MinimalApiPipelineTests.cs
|-- ef-core/
| `-- advanced-modeling-performance/
| |-- EfCoreHotPathMinimal.slnx
| |-- README.md
| |-- src/
| | `-- EfCoreHotPathMinimal/
| | |-- EfCoreHotPathMinimal.csproj
| | |-- Program.cs
| | |-- Data/
| | | |-- CatalogDatabase.cs
| | | `-- CatalogDbContext.cs
| | |-- Models/
| | | |-- Product.cs
| | | `-- ProductSummary.cs
| | |-- Queries/
| | | `-- CatalogQueries.cs
| | `-- Services/
| | `-- CatalogWorkflow.cs
| `-- tests/
| `-- EfCoreHotPathMinimal.Tests/
| |-- EfCoreHotPathMinimal.Tests.csproj
| `-- EfCoreHotPathTests.cs
|-- .github/
| `-- workflows/
| `-- build-samples.yml
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/EfCoreHotPathMinimal/EfCoreHotPathMinimal.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/EfCoreHotPathMinimal.Tests/EfCoreHotPathMinimal.Tests.csproj" />
</Folder>
</Solution>
211 changes: 211 additions & 0 deletions ef-core/advanced-modeling-performance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# EF Core Hot-Path Queries & Set-Based Operations

A focused EF Core companion demonstrating a lean read path, a stable compiled
query, a named soft-delete query filter, and server-side set-based updates and
deletes without external database infrastructure.

## Full tutorial

[EF Core Advanced Modeling & Performance: Owned Types, Converters, JSON/Temporal Tables, Compiled Queries](https://www.dotnet-guide.com/tutorials/ef-core/advanced-modeling-performance/)

## Version note

The full tutorial was written around .NET 8 / EF Core 8.

This companion targets:

```text
.NET 10
EF Core 10
Microsoft.EntityFrameworkCore.Sqlite 10.0.11
```

because that is the current DOTNET GUIDE repository baseline.

## Focus

```text
SQLite in-memory
-> named soft-delete filter
-> DTO projection
-> AsNoTracking
-> EF.CompileAsyncQuery
-> ExecuteUpdateAsync
-> ExecuteDeleteAsync
-> fresh-context verification
```

This sample is intentionally not a benchmark.

## Why SQLite?

SQLite gives the companion a real relational database without:

- credentials;
- a server;
- Docker;
- Testcontainers;
- generated database files.

The low-level in-memory connection remains open for the lifetime of each
scenario so multiple DbContext instances see the same database.

SQLite is still a different provider from SQL Server and PostgreSQL.

This sample does not validate provider-specific JSON, temporal-table,
concurrency-token, migration, query-plan, or batching behavior.

## Soft-delete filter

The companion targets EF Core 10 and uses a named query filter:

```csharp
entity.HasQueryFilter(
"SoftDeleteFilter",
product => !product.IsDeleted);
```

The purge path selectively disables it:

```csharp
IgnoreQueryFilters(
["SoftDeleteFilter"])
```

The EF Core 8 tutorial must continue to explain that older versions use one
combined filter expression when several filters apply to the same entity.

## Projection and tracking

The read path projects only:

```text
Sku
Name
PriceCents
StockQuantity
```

into `ProductSummary`.

It also uses `AsNoTracking` to make the read-only intent explicit.

The tests verify that no entity entries remain in the DbContext change tracker.

## Compiled query

The sample uses one:

```text
EF.CompileAsyncQuery
```

for a stable SKU lookup.

This does not mean every query should be manually compiled.

EF Core already caches ordinary queries by expression-tree shape. Explicit
compiled queries bypass the normal cache lookup and are intended for measured
hot paths with stable query shapes and simple scalar parameters.

The sample makes no fixed latency-savings claim.

## Set-based update

```text
ExecuteUpdateAsync
```

increments stock for all visible products below the threshold in one
set-based command.

The application does not load those products before updating them.

## Change-tracker caveat

`ExecuteUpdateAsync` does not synchronize entity instances that were already
tracked by the DbContext.

The test suite deliberately proves this behavior.

Use a clear context boundary, reload, or clear tracking when mixing set-based
operations with tracked entities.

## Set-based delete

The soft-deleted row is hidden by default.

The purge operation explicitly disables the named soft-delete filter and calls:

```text
ExecuteDeleteAsync
```

The affected-row count is asserted.

## Deterministic output

```text
EF Core Hot-Path Query Lab
Visible products: 4
Compiled lookup: SKU-003 | Dock | 12900 cents
Restocked products: 3
Purged soft-deleted products: 1
Final rows: 4
```

## Restore, build, and test

```powershell
dotnet restore `
.\EfCoreHotPathMinimal.slnx

dotnet build `
.\EfCoreHotPathMinimal.slnx `
--configuration Release `
--no-restore

dotnet test `
.\EfCoreHotPathMinimal.slnx `
--configuration Release `
--no-build
```

## Run

```powershell
dotnet run `
--project .\src\EfCoreHotPathMinimal\EfCoreHotPathMinimal.csproj `
--configuration Release `
--no-build
```

## Deliberately omitted

- SQL Server;
- PostgreSQL;
- temporal tables;
- JSON columns;
- owned/complex types;
- value converters;
- encryption;
- concurrency tokens;
- migrations;
- Testcontainers;
- split queries;
- logging/interceptors;
- benchmarking.

The complete tutorial covers the broader modeling and provider-specific
concepts.

## Verification

- Target framework: .NET 10
- EF Core provider: SQLite 10.0.11
- Direct application packages: 1
- Tests: 8
- External services: none
- Generated database files: none
- Benchmarks: none
- Last reviewed: 2026-08-14
Loading
Loading