QueryStore reads, Repository writes
Reads and writes live in different classes, and the naming tells you which is which. A QueryStore does read-only queries: method names start with Get, and every query is .AsNoTracking() (it never intends to mutate, so it skips EF’s change tracker). A Repository loads entities for an operation and saves them: Load* to read-for-write, Save*/MarkAs* to write. Anti-pattern #6: don’t mix AsNoTracking reads and writes in one class, and don’t put Get* on a repository or Load* on a query store.
public sealed class DecisionQueryStore : IDecisionQueryStore
{
public async Task<IReadOnlySet<string>> GetDecidedFlightIdentities()
{
var keys = await _dbContext.FlightDisruptionCostCalcs
.AsNoTracking() // read-only, no change tracking
.Where(x => x.DecidedAtUtc != null && x.RevokedAtUtc == null)
.Select(x => x.EjdpUniqueFlightKey)
.ToListAsync();
return keys.ToHashSet(StringComparer.OrdinalIgnoreCase);
}
}Try it yourself
Where does the method go
You need a method that returns a list of decided snapshots for a date range, read-only, for a list view. Where does it belong and what marks it?
Show answer
Correct answer: B — On the QueryStore, named GetDecidedSnapshots... , with AsNoTracking
A read-only query for a view goes on the QueryStore with a Get* name and AsNoTracking (anti-pattern #6). Load*/Save* and change tracking are the Repository's job for writes.
Why AsNoTracking
State the reason, not just the rule.
Why does every method in a QueryStore call .AsNoTracking()?
Reveal answer
The results are read-only: they're never mutated and re-saved, so there's no reason to pay EF's change-tracker cost, and it keeps query-path side effects off the write-side aggregate repository. AsNoTracking is faster and signals intent (this is a pure read).