Conversation
Carries the page plus the pre-paging total, which is what a caller needs to know how many pages exist. The total is free to compute: the filter has already run over the bucket.
GetAll and Query deep-clone the whole store on every call, which makes paging over them cost the entire bucket per page. QueryPaged filters and orders the live entities under the same lock and clones only what survives skip/take: O(bucket) cheap comparisons plus O(page) clones. The sort key is required rather than defaulted. Paging an unordered bucket is meaningless — enumeration order is not contractual and shifts as entries come and go, so a page could repeat or drop an entity with nothing in the result admitting it. Ties break on the entity key, since the caller's key is rarely unique and equal keys would otherwise fall back to that same unstable order. The filter runs under the state lock against live, uncloned entities, which is where the saving comes from and also the constraint: it must be a pure, cheap read. Documented on the interface. A test asserts exactly one clone per returned entity. Nothing else would notice this method quietly degrading into GetAll.
|
|
||
| public sealed class EntityStorePagedTests : IAsyncDisposable | ||
| { | ||
| private readonly string _dir = Path.Combine(Path.GetTempPath(), "squidstd-paged-" + Guid.NewGuid().ToString("N")); |
| _descriptor = new( | ||
| new PersistenceEntityDescriptor<Player, int>(serializer, serializer, 1, "Player", 1, p => p.Id) | ||
| ); | ||
| _journal = new(Path.Combine(_dir, "world.journal.bin")); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
GetAll()andQuery()deep-clone the whole store on every call —Query()doesBucket().Values.Select(_descriptor.Clone).ToArray()inside the lock, so despite returningIQueryableit is a full materialised snapshot, not a lazy view. Paging over either therefore costs the entire bucket per page.QueryPagedfilters and orders the live entities under the same lock and clones only what survivesskip/take: O(bucket) cheap comparisons plus O(page) clones.The sort key is required, not defaulted
Paging an unordered bucket is meaningless:
Dictionaryenumeration order is not contractual and shifts as entries come and go, so page 2 could repeat a row from page 1 or drop one entirely, with nothing in the result admitting it.Ties break on the entity's own key. The caller's key is rarely unique — two rows can share a name — and leaving equal keys to
Dictionaryorder would reintroduce exactly the instability the parameter exists to remove. This asksTKeyto be comparable; thenotnullconstraint is deliberately not tightened, since that would break every store in the library.Comparer<TKey>.Defaultthrows a clear error on the first comparison for an exotic key, rather than mis-ordering quietly — and every realistic key (Guid,int,string, a serial struct) is comparable.The filter runs under the lock
That is where the saving comes from, and also the constraint: it sees live, uncloned entities and blocks writes while it runs, so it must be a pure, cheap read — no I/O, no locks, no calls back into the store, no mutating its argument. Documented on the interface.
Tests
8 new, 88 in the persistence suite, 1388 in the whole suite, all green.
The one worth reading is
QueryPaged_ClonesOnlyThePage: a descriptor that countsClonecalls asserts exactly one per returned entity. It is the entire point of the method, and nothing else would notice it quietly degrading intoGetAll. Two more assert that pages neither overlap nor skip when sort keys tie.API surface
Adds
PagedResult<TEntity>and one member onIEntityStore. A new interface member breaks external implementers in principle; in practiceEntityStoreissealedwith aninternalconstructor, and the interface's own remarks already tell callers never to construct or register it themselves. A downstream test double is the only plausible casualty, which the minor bump carries.Releases as 0.37.0: both commits are
feat:.Also syncs
chore(release): 0.36.0back from main into develop — it had never returned, soDirectory.Build.propson develop still read 0.35.0.