feat: add UpsertAsync/UpsertBulkAsync - #142
Conversation
Insert-or-replace resolved with a single primary-index lookup per document inside one transaction, not a Find followed by a separate Insert/Update call — avoids the double lookup, extra lock/transaction cycle, and check-then-act race a naive wrapper would have. - DocumentCollection<TId,T>: UpsertAsync/UpsertBulkAsync, returning UpsertResult<TId> (Id, Inserted). Skips the lookup entirely when the entity's id is unset, since no existing document could match it. - DynamicCollection: same UpsertAsync/UpsertBulkAsync over BsonDocument, returning UpsertResult<BsonId>. - Refactors UpdateDataCore/UpdateAsync to split out the post-lookup write path (UpdateAtLocationCore) so Upsert's update branch reuses it without a second primary-index lookup. - CDC and metrics report the operation as Insert or Update based on what actually happened; no synthetic "Upsert" event type was added. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The typed upsert update path ignores UpdateAtLocationCore’s failure return and can report a successful replace when no update actually occurred.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds first-class upsert support to BLite’s typed (DocumentCollection<TId,T>) and schema-less (DynamicCollection) APIs, returning a new UpsertResult<TId> that reports the resolved id and whether the call inserted vs replaced. The implementation aims to resolve each document with a single primary-index lookup inside a transaction, and includes new tests and README examples.
Changes:
- Added
UpsertAsync/UpsertBulkAsynctoIDocumentCollection<TId,T>+DocumentCollection<TId,T>and toDynamicCollection. - Refactored update internals to allow “update-at-known-location” so upsert update paths don’t re-lookup keys.
- Added new tests for typed and dynamic upsert paths; updated README with usage examples.
File summaries
| File | Description |
|---|---|
| tests/BLite.Tests/UpsertTests.cs | New typed upsert test coverage (insert/replace/bulk behaviors). |
| tests/BLite.Tests/UpsertDynamicCollectionTests.cs | New schema-less upsert test coverage (insert/replace/bulk behaviors). |
| src/BLite.Core/DynamicCollection.cs | Adds UpsertAsync/UpsertBulkAsync and refactors update path to reuse a resolved location. |
| src/BLite.Core/Collections/UpsertResult.cs | Introduces UpsertResult<TId> return type for upsert operations. |
| src/BLite.Core/Collections/IDocumentCollection.cs | Extends the public typed collection interface with upsert APIs. |
| src/BLite.Core/Collections/DocumentCollection.cs | Implements typed upsert APIs and refactors update to support update-at-location reuse. |
| README.md | Documents upsert usage for typed and schema-less APIs. |
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…appen UpsertCore's update branch ignored UpdateAtLocationCore's return value. UpdateAtLocationCore returns false when the primary index resolves a location but the stored document can't be read (stale/corrupted index entry) — UpsertAsync/UpsertBulkAsync would then report a successful replace (Inserted: false) even though nothing was actually written. Now throws instead of lying about the outcome. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…I pages Adds the Upsert section previously stubbed as "not yet available" in CRUD.vue, and a matching section + API reference rows in DynamicAPI.vue, for the new UpsertAsync/UpsertBulkAsync API landing in BLite (EntglDb/BLite#142). Also adds deploy/publish-prod.sh + deploy/.env.example to publish this site to Plesk (blitedb.com), mirroring the pattern used in the WebSites repo but adapted for this project's Vite SSG build (npm run build -> dist/). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Updates package.json, Hero badge, App.vue/Studio.vue footer download links (Studio v5.1.0 assets), and the Installation.vue NuGet version snippet ahead of the BLite 5.1.0 release (adds UpsertAsync/UpsertBulkAsync, EntglDb/BLite#142). Left BLite.Server-specific version references (server-docs/VersionMatrix.vue, server-docs/Overview.vue) untouched — BLite.Server's compatibility with engine 5.1.0 hasn't been verified, that repo hasn't been touched here. Also left historical "BLite X.Y.Z adds/introduces ..." feature-origin callouts (Encryption/AuditTrail/GDPR docs, router SEO descriptions) as-is since they describe when a feature shipped, not the current version. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
Closes #139. Adds
UpsertAsync/UpsertBulkAsyncto bothDocumentCollection<TId,T>andDynamicCollection, returningUpsertResult<TId>(Id,Inserted).Not a
Findfollowed by a separateInsertAsync/UpdateAsynccall — each document is resolved with a single primary-index lookup inside one transaction: found → replace at that location, not found → insert. That avoids the double lookup, extra lock/transaction cycle, and check-then-act race a naive wrapper would carry. When the entity's id is unset, the lookup is skipped entirely since no existing document could match it.DocumentCollection<TId,T>:UpsertAsync,UpsertBulkAsyncon both the interface and implementation.DynamicCollection: same shape overBsonDocument, keyed byBsonId.UpdateDataCore/UpdateAsync(both classes) to split out the post-lookup write path (UpdateAtLocationCore) so Upsert's update branch reuses it without a second lookup.InsertorUpdatebased on what actually happened — no synthetic "Upsert" event type was introduced.Test plan
dotnet buildonBLite.Core,BLite,BLite.SourceGenerators(Debug + Release) — cleantests/BLite.Tests/UpsertTests.cs(typedDocumentCollection<TId,T>),tests/BLite.Tests/UpsertDynamicCollectionTests.cs(DynamicCollection) — insert path, replace path, no-duplicate-key behavior, bulk mix of inserts/updatesdotnet test tests/BLite.Tests— 2337 passed; only 3 pre-existing, unrelatedMultiProcessWalSharedMemoryTestsfailures (cross-process WAL lock timeouts, untouched by this diff)🤖 Generated with Claude Code