Database, File I/O, Async and Waits
The two wait-heavy paths are rewritten:
- One statement per page. The N+1 behind the grid is gone:
TicketQueryServiceissues a singleAsNoTrackingprojection with the customer name joined and only the displayed columns selected — 201 statements → 1, andTickets/Search125 ms → 66 ms. The filter and sort columns are now indexed. - The export never blocks.
Application.StartTaskreplaces.Result, oneStreamWriterreplaces 5,001 file opens, and progress is reported every ten percent — 522 ms → 171 ms, with the request thread free and the screen usable throughout.
Evidence: QueryTrace.md · ExportRework.md
Run it
cd "LearnWisej-Samples/Performance and Profiling Course/Module 6/WisejPerfLab"
dotnet run -c Release -f net10.0 --urls http://localhost:5806
Module 5 on 5805 is the before.
What to click
| Action | What you should see |
|---|---|
| Tickets → Search tickets | 66 ms — within the 300 ms budget, then 1 page(s) held 1 fetch(es) 1 statements — one statement for the page, where Module 5 needed 201 |
| Tickets → Export CSV | the button greys out, Cancel lights up, the progress bar moves in ten steps, then exported tickets-….csv in 171 ms |
| click something else while the export runs | it works — the request thread was never held |
| Cancel during an export | export cancelled after … ms — the partial file was left on disk |
| Break the database → Export CSV | the button comes back, the banner says why, and the PERF end record is written with the failure |
| Export CSV twice quickly | the second click is dropped, and the status line says so |
| Rows 50000 → Search tickets → Export CSV | the whole open population exported, still without blocking |
What changed since Module 5
Services/TicketQueryService.cs one AsNoTracking projection query: joined, narrowed, ordered, paged
+ SearchAsync for callers that can await
Services/ExportService.cs rewritten: takes TicketQueryService, one StreamWriter, ten progress
reports, a CancellationToken
Pages/TicketGridPage.cs the export runs in Application.StartTask; + Cancel; + IsDisposed checks
Pages/TicketGridPage.Designer.cs + btnCancelExport, the progress bar moved under the grid
Data/PerfLabContext.cs + IX_Tickets_Status_UpdatedAt
Data/PerfLabDatabase.cs + CREATE INDEX IF NOT EXISTS (EnsureCreated cannot add it to an
existing file; a real app would add a migration)
Known simplifications
- SQLite runs in process, so the 201-statement N+1 cost far less here than it would against a database server. The statement count is real; the milliseconds are flattering.
- Concurrent exports from several sessions were not tested — see
ExportRework.md.