Skip to content

The buffered Results family and FirstOrdinalResults return the Queue<T> they build - #109

Closed
electricessence wants to merge 5 commits into
masterfrom
feat/buffered-queue
Closed

electricessence wants to merge 5 commits into
masterfrom
feat/buffered-queue

Conversation

@electricessence

Copy link
Copy Markdown
Collaborator

If you generate a queue, let the caller use it. ResultsBuffered, Results<T>(IDbCommand), ResultsAsync<T>(DbCommand) and FirstOrdinalResults* used to wrap the queue they built in a one-shot DequeueEach() enumerable. They now return the queue.

What changes

24 public members change their return type: IEnumerable<X> becomes Queue<X>, and ValueTask<IEnumerable<X>> becomes ValueTask<Queue<X>>. Parameters, defaults and attributes are unchanged.

  • CoreExtensions: ResultsBuffered<T> (2), Results<T>(IDbCommand) (2), ResultsBufferedAsync<T> (3), ResultsAsync<T>(DbCommand) (3).
  • DataReaderExtensions, CommandExtensions, IExecuteReaderExtensions and ExpressiveDbCommandBase: the FirstOrdinalResults and FirstOrdinalResultsAsync forms, with and without T0 (14).
  • PublicAPI.Unshipped.txt: 24 *REMOVED* lines and 24 new lines.

Behaviour changes:

  1. Enumerating the queue does not empty it. A second loop sees the rows again, and Count is the number of rows read. First() and Take() no longer discard what they read.
  2. Rows stay in memory until the queue is dropped. Call Dequeue (or TryDequeue) to release them as you go.
  3. The object forms (FirstOrdinalResults()) hold null, never DBNull. The conversion happens while the queue is built; nothing is left to do lazily.
  4. In the non-provider FirstOrdinalResults<T0> path (a reader or command that is not a DbDataReader or DbCommand), a value that cannot be cast to T0 throws from the call, not while you enumerate. The provider path already did.
  5. The return type changed, so code compiled against 10.x or rc.3 must be recompiled. A few source shapes stop compiling: var x = command.Results<T>(); x = x.Where(...), ValueTask<IEnumerable<T>> v = command.ResultsAsync<T>() on a DbCommand or reader, and .AsTask() assigned to Task<IEnumerable<T>>.
  6. The queue is the library's own buffer, not a copy: it is yours to change.

The members marked [Obsolete] in #107 (IExecuteReader.Results<T>, ExpressiveDbCommand.ResultsAsync<T> x2) keep their IEnumerable<T> return type. They now return the replayable queue typed as IEnumerable<T>; the two async ones need a one-line body change. DequeueEach stays public and is no longer used inside the library.

Docs

  • whats-new-11.md: item 14 rewritten, and a new section "The buffered results are a queue" with the source-compatibility list and the cast-failure note.
  • foundation.md: the buffering sentence now says the rows are read and mapped first and returned as a Queue<T>.
  • Benchmarks/RESULTS.md: "draining queue" reworded; the 2026-11-07 re-measurement is marked "before rc.4". No benchmark was run.
  • New compiled sample docsite/samples/BufferedQueue.cs (two regions) with three checks in Program.cs.
  • XML docs: every <returns> says what is returned (replay, Dequeue, lifetime), and the summaries no longer say the transform is applied per iteration.

Tests

  • New BufferedQueueTests: a reflection test over the 24 members, replay and Count, Dequeue, null not DBNull on every receiver, async with useReadAsync true and false, a cancelled token, the eager cast failure, and the obsolete-bound IExecuteReader.Results<T> still returning its rows.
  • ResultsBuffered_DrainsAsItIsEnumerated is replaced by ResultsBuffered_ReturnsTheQueue_AndEnumeratingDoesNotEmptyIt. Two edge tests assert the queue type, the count and a second pass.
  • No ExpressiveDbCommand instance test for FirstOrdinalResultsAsync; it is covered by the reflection test and the existing leak tests.

Checks

From the gate (verify-gate.ps1) on the committed tree:

  • Release and Debug builds: 0 warnings, 0 errors (distinct warning lines: 0).
  • Core tests: 9271 passed, 0 failed, in Release and in Debug.
  • Samples build 0 warnings, 0 errors; "All 99 sample checks passed."
  • Style and analyzers on every project, all target frameworks, info level: 0 findings.
  • DocFX: 0 warnings, 0 errors.

That gate ran before master's #108 was merged into this branch. #108 touches no file this change touches, and the merge had no conflicts. A gate run on the merged tree is next; this PR stays a draft until it and an independent review pass.

Not in this change

The version bump and the "Changes since rc.3" note for rc.4. Removal or deprecation of DequeueEach. No benchmark or allocation numbers.

🤖 Generated with Claude Code

electricessence and others added 5 commits November 10, 2026 00:23
…T> they build, holding null instead of DBNull, instead of a one-shot dequeuing enumerable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…object forms hold null, and that a bad cast in the non-provider FirstOrdinalResults<T> throws at the call.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…g a queue that can be enumerated again and dequeued.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…compatibility changes and the cast-failure change listed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant