fix: make ExistsAsync(id) real-time for soft-delete models#270
Merged
fix: make ExistsAsync(id) real-time for soft-delete models#270
Conversation
Previously, ExistsAsync(id) fell back to the near-real-time Search API when the model implemented ISupportSoftDeletes, causing dirty reads during the refresh window. Now it uses the GET API with a source filter for the IsDeleted field and checks the soft-delete state in code. Also fixes ShouldReturnDocument to correctly handle DeletedOnly mode (previously it returned true for all documents in that mode).
…stsAsync with non-existent IDs Covers previously untested scenarios exposed by the ShouldReturnDocument fix: - GetByIdAsync correctly returns null for active docs in DeletedOnly mode - GetByIdsAsync correctly filters to only deleted docs in DeletedOnly mode - ExistsAsync(id) returns false for non-existent IDs on soft-delete models
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves Elasticsearch read consistency for soft-delete models by making ExistsAsync(id) use a real-time read path and by fixing soft-delete filtering behavior for DeletedOnly mode.
Changes:
- Updated
ElasticReadOnlyRepositoryBase.ExistsAsync(id)to use real-time GET with a minimal_sourceinclude for soft-delete models, falling back to Search only when routing is unavailable for parent/child mappings. - Fixed
ShouldReturnDocumentsoSoftDeleteQueryMode.DeletedOnlyreturns only deleted documents (and not all documents). - Added/expanded tests around
ExistsAsync(id),GetByIdAsync, andGetByIdsAsyncbehavior under different soft-delete modes; updated consistency documentation.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tests/Foundatio.Repositories.Elasticsearch.Tests/ReadOnlyRepositoryTests.cs | Adds coverage validating real-time ExistsAsync(id) behavior for soft deletes and correct DeletedOnly behavior for ID-based reads. |
| src/Foundatio.Repositories.Elasticsearch/Repositories/ElasticReadOnlyRepositoryBase.cs | Switches ExistsAsync(id) soft-delete path to real-time GET + _source include; fixes ShouldReturnDocument mode handling. |
| docs/guide/consistency.md | Updates documentation to reflect the new real-time behavior for ExistsAsync(id) with soft deletes. |
| .agents/skills/foundatio-repositories/SKILL.md | Updates internal guidance to match the new ExistsAsync(id) behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…y for child doc fallback
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.
Summary
_source_includes=isDeletedfilter for models implementingISupportSoftDeletes, eliminating dirty reads during the Elasticsearch refresh window.ExistsAsync(id)could return stale results for up to 1 second after a write.ShouldReturnDocumentto correctly handleSoftDeleteQueryMode.DeletedOnly— it previously returnedtruefor all documents regardless of delete state in that mode.Changes
ElasticReadOnlyRepositoryBase.ExistsAsync(Id): Restructured to use GET API with source filter for soft-delete models, only falling back to Search API for parent documents without routing.ElasticReadOnlyRepositoryBase.ShouldReturnDocument: Fixed to use a switch expression that properly handles all threeSoftDeleteQueryModevalues.ReadOnlyRepositoryTests:ExistsAsync_WithSoftDeletedDocument_IsRealTimeWithoutRefreshGetByIdAsync_WithDeletedOnlyMode_ReturnsOnlyDeletedDocumentsGetByIdsAsync_WithDeletedOnlyMode_ReturnsOnlyDeletedDocumentsExistsAsync_WithNonExistentIdOnSoftDeleteModel_ReturnsFalsedocs/guide/consistency.mdand.agents/skills/foundatio-repositories/SKILL.mdto reflect the new real-time behavior.Test plan
ExistsAsync_WithSoftDeletedDocument_IsRealTimeWithoutRefresh—ExistsAsync(id)returns false immediately after soft-deleting (withoutImmediateConsistency);IncludeSoftDeletesandDeletedOnlymodes behave correctlyGetByIdAsync_WithDeletedOnlyMode_ReturnsOnlyDeletedDocuments/GetByIdsAsync_WithDeletedOnlyMode_ReturnsOnlyDeletedDocuments—DeletedOnlyreturns only deleted documentsExistsAsync_WithNonExistentIdOnSoftDeleteModel_ReturnsFalse— missing IDs across all soft-delete modes