mirror of
https://github.com/dotnet/skills.git
synced 2026-09-20 09:49:54 +08:00
0b81eee89a
Fixes #892 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
141 lines
8.4 KiB
YAML
141 lines
8.4 KiB
YAML
name: optimizing-ef-core-queries
|
|
description: Evaluates the dotnet-data/optimizing-ef-core-queries skill
|
|
type: capability
|
|
config:
|
|
timeout: 30m
|
|
# The skill is exercised with one open-ended "audit" scenario rather than a set
|
|
# of single-fix micro-tasks. The stimulus stages a self-contained service class
|
|
# (its entity model and DbContext live in the same file) that deliberately packs
|
|
# most of the anti-patterns the skill addresses, and asks the agent to make it
|
|
# faster IN PLACE and explain each change.
|
|
#
|
|
# Grading is hybrid. The prompt/rubric judge scores review completeness, so the
|
|
# skill earns preference when its systematic coverage catches the subtler issues
|
|
# an unaided review tends to miss — for example recommending an index that code
|
|
# alone can't add. A micro-benchmark harness (staged into perf/) then times each
|
|
# rewritten method against a preserved copy of the original body over the same
|
|
# seeded in-memory SQLite database: one run-command grader per benchmarkable method
|
|
# requires the agent's version to return identical results AND run at least 10% faster
|
|
# (improved=True), giving the grader objective per-method signal. Eleven of the twelve
|
|
# methods have a result-preserving, signature-stable faster form and are benchmarked;
|
|
# GetOrderPage is the exception — its only real fix, keyset paging, pages by the last
|
|
# key seen rather than a page number, so the frozen signature rules out an in-place
|
|
# switch and the rubric grades the recommendation instead. The rubric additionally
|
|
# credits recognitions the benchmarks can't see, such as that keyset recommendation
|
|
# and a missing-index one.
|
|
stimuli:
|
|
- name: Audit a back-office operations service riddled with query problems
|
|
prompt: |
|
|
Our internal back-office service has gotten slow in production: the ops-dashboard tiles, the
|
|
order and product lookups, and the nightly maintenance jobs have all degraded as the tables
|
|
grew into the millions of rows. The code is in `SalesOperationsService.cs` (the entity model
|
|
and DbContext are in the same file so you can see the columns and relationships).
|
|
|
|
Go through the service and make it faster: apply your optimizations directly in
|
|
`SalesOperationsService.cs`, and for each method you change, explain what was slow and why
|
|
your fix helps. Keep the public API and the data model exactly as they are — same namespace,
|
|
class, entity types, records and DbContext, and the same method names, parameters and return
|
|
types (you may add private helpers or fields) — so the rest of the app and its tests keep
|
|
compiling. Every method you change must return the same results as it did before.
|
|
|
|
Work through every method: optimize the ones that can be improved, and briefly note any that
|
|
are already efficient.
|
|
environment:
|
|
files:
|
|
- src: audit-fixtures/SalesOperationsService.cs
|
|
dest: SalesOperationsService.cs
|
|
- src: benchmark-fixtures
|
|
dest: perf
|
|
graders:
|
|
- type: output-matches
|
|
config:
|
|
pattern: (N\+1|per[- ]customer|round[- ]trip|Cartesian|keyset|projection|index|scan|memory|SaveChanges)
|
|
- type: exit-success
|
|
- type: prompt
|
|
# One micro-benchmark per benchmarkable method (11 of the 12): each requires the
|
|
# agent's rewrite to return identical results and run at least 10% faster than the
|
|
# preserved original body (improved=True). GetOrderPage has no in-place faster form
|
|
# under its frozen page-number signature, so it is graded by the rubric instead.
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method GetCustomerSales
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method GetCustomerDetail
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method HasOrders
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method GetUnpaidInvoices
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method ListActiveProducts
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method GetProductForCard
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method ApplyCategoryDiscount
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method CountActiveProducts
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method GetOrdersOverTotal
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method GetPendingOrders
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
- type: run-command
|
|
config:
|
|
command: dotnet run -c Release --project perf -- --method SearchProducts
|
|
expected_exit_code: 0
|
|
stdout_contains: improved=True
|
|
timeout: 6m
|
|
rubric:
|
|
- "Identifies GetCustomerSales as issuing a separate orders query for every customer (an N+1) and recommends collapsing it into a single set-based query"
|
|
- "Recognizes that GetCustomerSales computes each customer's order count and total in memory after loading the rows, and that those aggregates should be computed by the database"
|
|
- "Identifies that GetCustomerSales filters orders by the year of a date column, wrapping the column in a function that prevents an index seek, and recommends a sargable date-range comparison instead"
|
|
- "Flags GetCustomerDetail as loading two independent related collections in one query, causing a Cartesian row explosion, and recommends splitting it into separate queries"
|
|
- "Notes that HasOrders uses a count-everything existence check where a short-circuiting existence test would be cheaper"
|
|
- "Recognizes that GetOrdersOverTotal pulls every order into memory before filtering by total, and that the filter should run in the database"
|
|
- "Recognizes that GetOrderPage uses offset paging that makes the database walk and discard every row before the requested page, so deep pages get progressively slower, and recommends keyset/seek pagination that continues from the last row already shown"
|
|
- "Recommends adding an index to support the frequent pending-orders status filter and date ordering"
|
|
- "Notes that GetUnpaidInvoices returns entities for a read-only export while still tracking them, and recommends running it without change tracking"
|
|
- "Identifies that ListActiveProducts loads full Product entities — including the wide Description and Image columns — and only projects to a few fields in memory afterward, and recommends projecting to those fields in the database so the wide columns are never read"
|
|
- "Recognizes that SearchProducts materializes full product rows (including the wide description and image columns) before projecting, and should project to the needed fields in the database"
|
|
- "Recognizes that GetProductForCard is a minimal query on an extremely hot path where the remaining cost is EF Core re-translating the same query on every call, and recommends eliminating that repeated per-call translation cost while returning identical results"
|
|
- "Identifies that ApplyCategoryDiscount saves changes once per product inside the loop, issuing a round-trip per row, and recommends a single set-based update"
|
|
- "Recognizes that CountActiveProducts loads the entire product table into memory before counting, and that the count should be performed by the database"
|
|
timeout: 600
|