Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
acdf1c6
feat: Update schema to version 104 and enhance MergeSearchParams stor…
jestradaMS Feb 26, 2026
a76ad96
Merge branch 'main' into users/jestrada/atomicsearchparameteroperations
jestradaMS Mar 3, 2026
b9e1733
fixing 104.sql
jestradaMS Mar 3, 2026
efc97f3
Enhance reindexing and search parameter operations with retry policie…
jestradaMS Mar 3, 2026
ce3d1af
Refactor search parameter operations to consolidate validation logic …
jestradaMS Mar 4, 2026
5d806a2
fixing appsettings that should not have been checked in
jestradaMS Mar 4, 2026
ef9b530
fixing white space after comment
jestradaMS Mar 4, 2026
35cd84b
Implement pending search parameter status handling in bundle operatio…
jestradaMS Mar 6, 2026
0d7ca29
Refactor transaction handling in MergeSearchParams to improve concurr…
jestradaMS Mar 6, 2026
64fef20
Clarify comments regarding pending search parameter status handling i…
jestradaMS Mar 6, 2026
3ba9312
Update ADR 2602 to implement atomic SearchParameter CRUD operations, …
jestradaMS Mar 6, 2026
fd27c7b
Update schema version to 108 and adjust related constants and conditions
jestradaMS Mar 6, 2026
318591e
ADR 2602: Atomic SearchParameter CRUD Operations
jestradaMS Mar 6, 2026
d994a32
Add ADR 2603: Atomic SearchParameter CRUD Operations and Cache Refres…
jestradaMS Mar 6, 2026
ea6c08f
Remove ADR 2602: Atomic SearchParameter CRUD Operations document
jestradaMS Mar 6, 2026
59e0acb
Merge branch 'main' into users/jestrada/atomicsearchparameteradr
jestradaMS Mar 10, 2026
0a24ed5
Merge branch 'main' into users/jestrada/atomicsearchparameteroperations
jestradaMS Mar 10, 2026
9b74e37
Update schema versioning to V107
jestradaMS Mar 10, 2026
67bef92
Adding OR ALTER to migration diff file.
jestradaMS Mar 10, 2026
04aa622
Remove redundant search parameter update calls in reindex handlers an…
jestradaMS Mar 10, 2026
999ac0c
Merge branch 'users/jestrada/atomicsearchparameteradr' into users/jes…
jestradaMS Mar 11, 2026
c5fe031
removing old 2602 adr for atomic search parameters
jestradaMS Mar 11, 2026
f1e5520
adding test for ensuring cache is not mutated when status manager is …
jestradaMS Mar 11, 2026
c9957c7
removing 107 files
jestradaMS Mar 17, 2026
7f912b9
Merge branch 'main' into users/jestrada/atomicsearchparameteroperations
jestradaMS Mar 17, 2026
5784146
Bump schema version to 108
jestradaMS Mar 17, 2026
b14f3cd
Implement search parameter operations to handle reindexing conflicts …
jestradaMS Mar 17, 2026
b00ce93
Refactor GetSearchParametersByUrls method to simplify URL resolution …
jestradaMS Mar 17, 2026
be7f304
Improve URL resolution logic from GetSearchParameterByUrls method
jestradaMS Mar 18, 2026
47cc2ac
Fixing SP State update handler tests
jestradaMS Mar 18, 2026
09a1c10
Fixing tests
jestradaMS Mar 18, 2026
7cae0ad
adding logging to reindex failures
jestradaMS Mar 18, 2026
5d83f96
adding Sergey's test for 500 serach parameters
jestradaMS Mar 18, 2026
a9239b7
Refactor reindex job to batch update disabled and deleted search para…
jestradaMS Mar 18, 2026
213164c
Updating new test for large custom search params
jestradaMS Mar 19, 2026
82245bb
Refactor ReindexTests to support dynamic search parameter handling an…
jestradaMS Mar 19, 2026
d0daa62
skipping flaky test to test run again
jestradaMS Mar 19, 2026
ade3cfe
updating to use output in ReindexTests opposed to System Diag
jestradaMS Mar 19, 2026
2734794
Updates per Sergey's comments
jestradaMS Mar 19, 2026
46df45c
removing test
jestradaMS Mar 19, 2026
899dc01
fixing MergeResourcesWrapperAsync
jestradaMS Mar 19, 2026
cd0d5c9
testing increased retries for reliability of tests
jestradaMS Mar 20, 2026
c4c8bb8
Removing old stored proc per feedback on PR
jestradaMS Mar 20, 2026
8dda219
Enhance search parameter operations with cache refresh cycle handling
jestradaMS Mar 20, 2026
a3290ea
Add ADR 2603 to address reindex cache race conditions and improve not…
jestradaMS Mar 20, 2026
58f9b6f
Fixing integration tests
jestradaMS Mar 20, 2026
0f5145b
adding timeout to ensure we don't hang if reresh never signals.
jestradaMS Mar 20, 2026
00d7275
Disposal update in integration tests
jestradaMS Mar 20, 2026
459adee
Ensure proper disposal of timer in SearchParameterCacheRefreshBackgro…
jestradaMS Mar 20, 2026
dddee07
fixing refreshtimer disoposal
jestradaMS Mar 20, 2026
baedead
fixing tests
jestradaMS Mar 20, 2026
4d97074
Refactor Search Parameter Cache Refresh Logic and Enhance Cache Consi…
jestradaMS Mar 21, 2026
224b41f
Fixing style cop issue
jestradaMS Mar 21, 2026
192e009
Fixing signals to ensure they log even if no changes.
jestradaMS Mar 23, 2026
9b6402c
Testing with LegacyInitializeSearchParameterStatuses
jestradaMS Mar 23, 2026
0f2d317
comment out code
jestradaMS Mar 23, 2026
92e7c98
updating convergence logic in operations
jestradaMS Mar 23, 2026
a076be6
trailing space :(
jestradaMS Mar 23, 2026
7583792
adding logging to cache consistency check
jestradaMS Mar 24, 2026
2e31e83
fixing casting in proc
jestradaMS Mar 24, 2026
c47c185
fix white space diff
jestradaMS Mar 24, 2026
6d442cc
Changing convergence check time to 30 second delay
jestradaMS Mar 24, 2026
95ddad7
Enhance cache consistency checks with sync timestamps and active host…
jestradaMS Mar 24, 2026
9ad7a76
Refactor cache update logic to ensure accurate timestamp logging and …
jestradaMS Mar 24, 2026
01ea4db
Reducing logging only when there is an active waiter
jestradaMS Mar 24, 2026
da818c1
Update syncStartDate to align with active host detection for cache co…
jestradaMS Mar 24, 2026
c2033df
Adding back blind wait for Cosmos
jestradaMS Mar 25, 2026
a1e6977
Fix: Update resource type check to remove unnecessary whitespace cond…
jestradaMS Mar 25, 2026
218172b
removing 108 on this branch to merge in from main
jestradaMS Mar 27, 2026
aebd351
Merge branch 'main' into users/jestrada/atomicsearchparameteroperations
jestradaMS Mar 27, 2026
42a1235
updated to schema 109 post main merge
jestradaMS Mar 27, 2026
dd30efc
Refactor transaction handling in MergeSearchParams to simplify rollba…
jestradaMS Mar 27, 2026
2db0796
Distributed search param cache sync (#5485)
SergeyGaluzo Apr 13, 2026
fe33165
Merge branch 'main' into users/jestrada/atomicsearchparameteroperations
jestradaMS Apr 13, 2026
489a9f9
Remove redundant search parameter conflict tests from ReindexTests
jestradaMS Apr 13, 2026
8fe83b7
Merge branch 'main' into users/jestrada/atomicsearchparameteroperations
jestradaMS Apr 14, 2026
491d06d
Add missing dependency for IScoped<IFhirOperationDataStore> in search…
jestradaMS Apr 14, 2026
82cb42d
Merge branch 'main' into users/jestrada/atomicsearchparameteroperations
jestradaMS Apr 14, 2026
5bff060
Merge branch 'main' into users/jestrada/atomicsearchparameteroperations
jestradaMS Apr 14, 2026
39eabb4
Add TaskHosting__MaxRunningTaskCount parameter to deployment configur…
jestradaMS Apr 14, 2026
598b004
Refactor bulk delete operations to improve search parameter handling …
jestradaMS Apr 20, 2026
b8611eb
Add search service dependency to BulkDeleteControllerTests for improv…
jestradaMS Apr 20, 2026
a6ec8d9
Implement ambient bundle SQL transaction context and integrate into b…
jestradaMS Apr 20, 2026
fa70e04
Merge branch 'main' into users/jestrada/atomicsearchparameteroperations
jestradaMS Apr 20, 2026
a0baeaf
Add BundleSqlTransactionContext and mock scope provider to SqlServerF…
jestradaMS Apr 20, 2026
6869356
adding back GetAndApplySearchParameterUpdates to ReindexSingleResourc…
jestradaMS Apr 21, 2026
385b094
Add flag to track presence of SearchParameter resources in bundles
jestradaMS Apr 21, 2026
23cdcda
Skip processing in DrainPendingSearchParameterStatuses if no SearchPa…
jestradaMS Apr 22, 2026
4c8bbfa
Remove BundleSqlTransactionContext and related interfaces from the co…
jestradaMS Apr 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# ADR 2602: Atomic SearchParameter CRUD Operations
Labels: [SQL](https://github.com/microsoft/fhir-server/labels/Area-SQL) | [Core](https://github.com/microsoft/fhir-server/labels/Area-Core) | [SearchParameter](https://github.com/microsoft/fhir-server/labels/Area-SearchParameter)

## Context
SearchParameter create, update, and delete operations require two coordinated writes: the SearchParameter status row (`dbo.SearchParam` table) and the resource itself (`dbo.Resource` via `dbo.MergeResources`). Previously, these writes occurred in separate steps in the request pipeline, creating partial-failure windows where one could succeed while the other fails — producing orphaned or inconsistent state.

Composing these writes into a single atomic SQL operation introduced a new problem for transaction bundles: `dbo.MergeSearchParams` acquires an exclusive lock on `dbo.SearchParam` per entry, and that lock is held by the outer bundle transaction. When the next entry's behavior pipeline calls `GetAllSearchParameterStatus` on a separate connection, it blocks on the same table, causing a timeout. This required a deferred-flush approach for transaction bundles.

Additionally, SearchParameter CRUD behaviors previously performed direct in-memory cache mutations (`AddNewSearchParameters`, `DeleteSearchParameter`, etc.) during the request pipeline. This duplicated responsibility with the `SearchParameterCacheRefreshBackgroundService`, which already polls the database and applies cache updates across all instances.

Key considerations:
- Eliminating partial-commit windows between status and resource persistence.
- Handling the lock contention on `dbo.SearchParam` introduced by composed writes in transaction bundles.
- Simplifying cache ownership by removing direct cache mutations from the CRUD request path.
- Preserving existing behavior for non-SearchParameter resources and Cosmos DB paths.

## Decision
We implement three complementary changes:

### 1. Composed writes for single operations (SQL)
For individual SearchParameter CRUD, behaviors queue pending status updates in request context (`SearchParameter.PendingStatusUpdates`) instead of persisting directly. `SqlServerFhirDataStore` detects pending statuses and calls `dbo.MergeSearchParams` (which internally calls `dbo.MergeResources`) so both writes execute in one stored-procedure-owned transaction.

### 2. Deferred flush for transaction bundles
For transaction bundles, per-entry resource upserts call `dbo.MergeResources` only (no SearchParam table touch), avoiding the exclusive lock. `BundleHandler` accumulates pending statuses across all entries and flushes them in a single `dbo.MergeSearchParams` call at the end of the bundle, still within the outer transaction scope.

```mermaid
graph TD;
A[SearchParameter Operation] -->|Single operation| B[Behavior queues pending status in request context];
B --> C[SqlServerFhirDataStore calls MergeSearchParams];
C --> D[MergeSearchParams: status + MergeResources in one transaction];
A -->|Transaction bundle| E[Per-entry: Behavior queues status, UpsertAsync calls MergeResources only];
E --> F[BundleHandler drains pending statuses after each entry];
F --> G[After all entries: single MergeSearchParams flush within outer transaction];
G --> H[Transaction commits atomically];
```

### 3. Cache update removal from CRUD path
SearchParameter CRUD behaviors no longer perform direct in-memory cache mutations. All cache updates (`AddNewSearchParameters`, `DeleteSearchParameter`, `UpdateSearchParameterStatus`) are now solely owned by the `SearchParameterCacheRefreshBackgroundService`, which periodically polls the database via `GetAndApplySearchParameterUpdates`. This simplifies the CRUD path and ensures consistent cache convergence across distributed instances.

### Scope
- **SQL Server**: Full atomic guarantees for create, update, and delete of SearchParameter resources, both single and in transaction bundles.
- **Cosmos DB**: Pending statuses are flushed after resource upsert (improved sequencing, not a single transactional unit).
- **Unchanged**: Non-SearchParameter CRUD, existing SearchParameter status lifecycle states, cache convergence model.

## Status
Pending acceptance

## Consequences
- **Positive Impacts:**
- Eliminates orphaned status/resource records from partial commits.
- Clarifies ownership: behaviors queue intent, data stores persist atomically, background service owns cache.
- Deferred-flush approach avoids lock contention introduced by composed writes in transaction bundles.
- Removing cache mutations from CRUD simplifies the request path and eliminates a class of cache-divergence bugs.

- **Potential Drawbacks:**
- Increased complexity in request context, data store, and bundle handler coordination.
- SQL schema migration required (`MergeSearchParams` expanded to accept resource TVPs).
- Eventual consistency window: cache may lag behind database until the next background refresh cycle.
- Cosmos DB path remains best-effort sequencing rather than true atomic commit.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -63,7 +63,8 @@ internal static void Build(
searchParameters,
uriDictionary,
modelInfoProvider,
isSystemDefined).ToLookup(
isSystemDefined,
logger).ToLookup(
entry => entry.ResourceType,
entry => entry.SearchParameter);

Expand Down Expand Up @@ -121,7 +122,8 @@ private static SearchParameterInfo GetOrCreateSearchParameterInfo(SearchParamete
IReadOnlyCollection<ITypedElement> searchParamCollection,
ConcurrentDictionary<string, SearchParameterInfo> uriDictionary,
IModelInfoProvider modelInfoProvider,
bool isSystemDefined = false)
bool isSystemDefined,
ILogger logger)
{
var issues = new List<OperationOutcomeIssue>();
var searchParameters = searchParamCollection.Select((x, entryIndex) =>
Expand Down Expand Up @@ -151,8 +153,27 @@ private static SearchParameterInfo GetOrCreateSearchParameterInfo(SearchParamete
{
SearchParameterInfo searchParameterInfo = GetOrCreateSearchParameterInfo(searchParameter, uriDictionary);

// Mark spec-defined search parameters as system-defined
searchParameterInfo.IsSystemDefined = isSystemDefined;
// Mark spec-defined search parameters as system-defined.
// Once marked, this should remain true across subsequent Build calls.
bool wasSystemDefined = searchParameterInfo.IsSystemDefined;
searchParameterInfo.IsSystemDefined |= isSystemDefined;

if (!wasSystemDefined && searchParameterInfo.IsSystemDefined)
{
logger.LogDebug(
"SearchParameter IsSystemDefined enabled: Url={Url}, Code={Code}, BuildIsSystemDefined={BuildIsSystemDefined}",
searchParameterInfo.Url?.OriginalString,
searchParameterInfo.Code,
isSystemDefined);
}
else if (wasSystemDefined && !isSystemDefined)
{
logger.LogWarning(
"SearchParameter IsSystemDefined downgrade ignored: Url={Url}, Code={Code}, BuildIsSystemDefined={BuildIsSystemDefined}",
searchParameterInfo.Url?.OriginalString,
searchParameterInfo.Code,
isSystemDefined);
}

if (searchParameterInfo.Code == "_profile" && searchParameterInfo.Type == SearchParamType.Reference)
{
Expand All @@ -174,6 +195,8 @@ private static SearchParameterInfo GetOrCreateSearchParameterInfo(SearchParamete

EnsureNoIssues();

SearchParameterInfo.ResourceTypeSearchParameter.IsSystemDefined = true;

var validatedSearchParameters = new List<(string ResourceType, SearchParameterInfo SearchParameter)>
{
// _type is currently missing from the search params definition bundle, so we inject it in here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ public sealed class ReindexOrchestratorJob : IJob
/// </summary>
private static readonly AsyncPolicy _searchParameterStatusRetries = Policy.WrapAsync(_requestRateRetries, _timeoutRetries);

/// <summary>
/// Retry policy for reindex query execution.
/// Handles transient SQL timeouts and Cosmos DB request rate limiting.
/// </summary>
private static readonly AsyncPolicy _reindexQueryRetries = Policy.WrapAsync(_requestRateRetries, _timeoutRetries);

private HashSet<long> _processedJobIds = new HashSet<long>();
private HashSet<string> _processedSearchParameters = new HashSet<string>();
private List<JobInfo> _jobsToProcess;
Expand Down Expand Up @@ -727,7 +733,8 @@ private async Task<SearchResult> GetResourceCountForQueryAsync(ReindexJobQuerySt
{
try
{
return await searchService.Value.SearchForReindexAsync(queryParametersList, searchParameterHash, countOnly: countOnly, cancellationToken, true);
return await _reindexQueryRetries.ExecuteAsync(
async () => await searchService.Value.SearchForReindexAsync(queryParametersList, searchParameterHash, countOnly: countOnly, cancellationToken, true));
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Collections.Generic;
using EnsureThat;
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
using Microsoft.Health.Fhir.Core.Models;

namespace Microsoft.Health.Fhir.Core.Features.Persistence
Expand Down Expand Up @@ -46,6 +48,8 @@ public ResourceWrapperOperation(

public BundleResourceContext BundleResourceContext { get; }

public IReadOnlyList<ResourceSearchParameterStatus> PendingSearchParameterStatuses { get; internal set; }

#pragma warning disable CA1024 // Use properties where appropriate
public DataStoreOperationIdentifier GetIdentifier()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EnsureThat;
using Hl7.Fhir.ElementModel;
using MediatR;
using Microsoft.Health.Core.Features.Context;
using Microsoft.Health.Fhir.Core.Exceptions;
using Microsoft.Health.Fhir.Core.Extensions;
using Microsoft.Health.Fhir.Core.Features.Context;
using Microsoft.Health.Fhir.Core.Features.Persistence;
using Microsoft.Health.Fhir.Core.Features.Search.Registry;
using Microsoft.Health.Fhir.Core.Messages.Create;
using Microsoft.Health.Fhir.Core.Messages.Upsert;
using Microsoft.Health.Fhir.Core.Models;
Expand All @@ -19,25 +26,41 @@ namespace Microsoft.Health.Fhir.Core.Features.Search.Parameters
public class CreateOrUpdateSearchParameterBehavior<TCreateResourceRequest, TUpsertResourceResponse> : IPipelineBehavior<CreateResourceRequest, UpsertResourceResponse>,
IPipelineBehavior<UpsertResourceRequest, UpsertResourceResponse>
{
private ISearchParameterOperations _searchParameterOperations;
private IFhirDataStore _fhirDataStore;
private readonly ISearchParameterOperations _searchParameterOperations;
private readonly IFhirDataStore _fhirDataStore;
private readonly ISearchParameterStatusManager _searchParameterStatusManager;
private readonly RequestContextAccessor<IFhirRequestContext> _requestContextAccessor;
private readonly IModelInfoProvider _modelInfoProvider;

public CreateOrUpdateSearchParameterBehavior(ISearchParameterOperations searchParameterOperations, IFhirDataStore fhirDataStore)
public CreateOrUpdateSearchParameterBehavior(
ISearchParameterOperations searchParameterOperations,
IFhirDataStore fhirDataStore,
ISearchParameterStatusManager searchParameterStatusManager,
RequestContextAccessor<IFhirRequestContext> requestContextAccessor,
IModelInfoProvider modelInfoProvider)
{
EnsureArg.IsNotNull(searchParameterOperations, nameof(searchParameterOperations));
EnsureArg.IsNotNull(fhirDataStore, nameof(fhirDataStore));
EnsureArg.IsNotNull(searchParameterStatusManager, nameof(searchParameterStatusManager));
EnsureArg.IsNotNull(requestContextAccessor, nameof(requestContextAccessor));
EnsureArg.IsNotNull(modelInfoProvider, nameof(modelInfoProvider));

_searchParameterOperations = searchParameterOperations;
_fhirDataStore = fhirDataStore;
_searchParameterStatusManager = searchParameterStatusManager;
_requestContextAccessor = requestContextAccessor;
_modelInfoProvider = modelInfoProvider;
}

public async Task<UpsertResourceResponse> Handle(CreateResourceRequest request, RequestHandlerDelegate<UpsertResourceResponse> next, CancellationToken cancellationToken)
{
if (request.Resource.InstanceType.Equals(KnownResourceTypes.SearchParameter, StringComparison.Ordinal))
{
// Before committing the SearchParameter resource to the data store, add it to the SearchParameterDefinitionManager
// and parse the fhirPath, as well as validate the parameter type
await _searchParameterOperations.AddSearchParameterAsync(request.Resource.Instance, cancellationToken);
// Before committing the SearchParameter resource to the data store, validate the parameter type
await _searchParameterOperations.ValidateSearchParameterAsync(request.Resource.Instance, cancellationToken);
Comment thread
feordin marked this conversation as resolved.

var url = request.Resource.Instance.GetStringScalar("url");
await QueueStatusAsync(url, SearchParameterStatus.Supported, cancellationToken);
}

// Allow the resource to be updated with the normal handler
Expand Down Expand Up @@ -67,19 +90,70 @@ public async Task<UpsertResourceResponse> Handle(UpsertResourceRequest request,

if (prevSearchParamResource != null && prevSearchParamResource.IsDeleted == false)
{
// Update the SearchParameterDefinitionManager with the new SearchParameter in order to validate any changes
// to the fhirpath or the datatype
await _searchParameterOperations.UpdateSearchParameterAsync(request.Resource.Instance, prevSearchParamResource.RawResource, cancellationToken);
// Validate any changes to the fhirpath or the datatype
await _searchParameterOperations.ValidateSearchParameterAsync(request.Resource.Instance, cancellationToken);

var previousUrl = _modelInfoProvider.ToTypedElement(prevSearchParamResource.RawResource).GetStringScalar("url");
var newUrl = request.Resource.Instance.GetStringScalar("url");

if (!string.IsNullOrWhiteSpace(previousUrl) && !previousUrl.Equals(newUrl, StringComparison.Ordinal))
{
await QueueStatusAsync(previousUrl, SearchParameterStatus.Deleted, cancellationToken);
}

await QueueStatusAsync(newUrl, SearchParameterStatus.Supported, cancellationToken);
}
else
{
// No previous version exists or it was deleted, so add it as a new SearchParameter
await _searchParameterOperations.AddSearchParameterAsync(request.Resource.Instance, cancellationToken);
await _searchParameterOperations.ValidateSearchParameterAsync(request.Resource.Instance, cancellationToken);

var url = request.Resource.Instance.GetStringScalar("url");
await QueueStatusAsync(url, SearchParameterStatus.Supported, cancellationToken);
}
}

// Now allow the resource to updated per the normal behavior
return await next(cancellationToken);
}

private async Task QueueStatusAsync(string url, SearchParameterStatus status, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(url))
{
return;
}

var context = _requestContextAccessor.RequestContext;
if (context == null)
{
return;
}

if (!context.Properties.TryGetValue(SearchParameterRequestContextPropertyNames.PendingStatusUpdates, out var value) ||
value is not List<ResourceSearchParameterStatus> pendingStatuses)
{
pendingStatuses = new List<ResourceSearchParameterStatus>();
context.Properties[SearchParameterRequestContextPropertyNames.PendingStatusUpdates] = pendingStatuses;
}

var currentStatuses = await _searchParameterStatusManager.GetAllSearchParameterStatus(cancellationToken);
var existing = currentStatuses.FirstOrDefault(s => string.Equals(s.Uri?.OriginalString, url, StringComparison.Ordinal));

var update = new ResourceSearchParameterStatus
{
Uri = new Uri(url),
Status = status,
LastUpdated = existing?.LastUpdated ?? DateTimeOffset.UtcNow,
IsPartiallySupported = existing?.IsPartiallySupported ?? false,
SortStatus = existing?.SortStatus ?? SortParameterStatus.Disabled,
};

lock (pendingStatuses)
{
pendingStatuses.RemoveAll(s => string.Equals(s.Uri?.OriginalString, url, StringComparison.Ordinal));
pendingStatuses.Add(update);
}
}
}
}
Loading
Loading