Skip to content

Postie.EntityFrameworkCore: untracked IQueryable injection for read-side handlers #11

Description

@AndrewMcLachlan

What problem would this solve?

CQRS's read/write separation is easy to state and easy to erode: the moment a query handler takes a DbContext, it can track entities, mutate state, and couple itself to the whole context surface. Keeping the read side honest currently requires per-app DI plumbing. A small opt-in package can make the pattern the path of least resistance: query handlers receive an untracked IQueryable and never see the DbContext — a separation of concerns between the context and your data.

Proposed API or behavior

// registration — explicit, per entity
builder.Services.AddReadQuery<BankDbContext, Transaction>();

// or the convenience sweep: registers IQueryable<T> for every DbSet<T> on the context
builder.Services.AddReadQueries<BankDbContext>();
// (TryAdd semantics, so explicit registrations made first win over the sweep)

// handler side — no DbContext in sight, tracking impossible by construction
public class GetTransactionsHandler(IQueryable<Transaction> transactions) : IQueryHandler<GetTransactions, PagedResult<Transaction>>
{
    public async ValueTask<PagedResult<Transaction>> Handle(GetTransactions query, CancellationToken cancellationToken) => ...
}

Each registration resolves as context.Set<TEntity>().AsNoTracking() from the scoped context. Dependency is Microsoft.EntityFrameworkCore only (no providers), versioned per TFM (net8/EF 8, net9/EF 9, net10/EF 10). Not coupled to the dispatcher — it also works for plain services — the CQRS read-side discipline is the framing, not a code dependency.

Alternatives considered

  • Repository interfaces per aggregate: heavier ceremony for pure reads and re-invents what IQueryable already expresses.
  • Injecting the DbContext and self-policing AsNoTracking in every handler: exactly the erosion this prevents; nothing enforces it.
  • Leaving it as per-app plumbing: the status quo; the registration is small but identical in every app, and the pattern deserves a named, documented home.

Contribution

  • I'm willing to send a PR for this

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions