Skip to content

Conversation

@PTaladay
Copy link
Contributor

@PTaladay PTaladay commented Dec 14, 2025

Description

Previously we optimized reads to use store proc instead of sql generator for simple resource searches. This change works for multiple ids in a single call using get resources.

Related issues

Addresses [issue AB#179104].

Testing

Describe how this change was tested.

FHIR Team Checklist

  • Update the title of the PR to be succinct and less than 65 characters
  • Add a milestone to the PR for the sprint that it is merged (i.e. add S47)
  • Tag the PR with the type of update: Bug, Build, Dependencies, Enhancement, New-Feature or Documentation
  • Tag the PR with Open source, Azure API for FHIR (CosmosDB or common code) or Azure Healthcare APIs (SQL or common code) to specify where this change is intended to be released.
  • Tag the PR with Schema Version backward compatible or Schema Version backward incompatible or Schema Version unchanged if this adds or updates Sql script which is/is not backward compatible with the code.
  • When changing or adding behavior, if your code modifies the system design or changes design assumptions, please create and include an ADR.
  • CI is green before merge Build Status
  • Review squash-merge requirements

Semver Change (docs)

Patch|Skip|Feature|Breaking (reason)

@PTaladay PTaladay added this to the FY26\Q2\2Wk\2Wk12 milestone Dec 14, 2025
@PTaladay PTaladay added Enhancement Enhancement on existing functionality. Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs labels Dec 14, 2025
@PTaladay PTaladay marked this pull request as ready for review January 5, 2026 18:50
@PTaladay PTaladay requested a review from a team as a code owner January 5, 2026 18:50
try
{
// Create a DataTable for the table-valued parameter
var resourceKeysTable = new DataTable();

Check warning

Code scanning / CodeQL

Missing Dispose call on local IDisposable Warning

Disposable 'DataTable' is created but not disposed.

Copilot Autofix

AI 2 days ago

In general, to fix a “missing Dispose call” for a local IDisposable, wrap the creation and use of the object in a using statement (or using declaration in newer C#), so that Dispose is called automatically when execution leaves the using scope, even if an exception is thrown.

For this specific case in SqlServerSearchService.cs, the best fix is to wrap the DataTable’s lifetime in a using block. That means replacing the standalone var resourceKeysTable = new DataTable(); and subsequent code that uses resourceKeysTable with a using (var resourceKeysTable = new DataTable()) { ... } block, moving all uses of resourceKeysTable (column definitions, row population, and parameter creation) inside that block. This keeps behavior the same: the DataTable exists for the duration of building the TVP parameter and calling AddWithValue, and then is disposed when the block exits. No additional imports or helper methods are needed, since DataTable and IDisposable are already available via existing using directives.

Concretely, within src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs, in the shown try block around line 990, introduce a using block starting at line 993, move lines 994–1002 and 1005–1009 inside that block, and adjust indentation accordingly. No other code outside that local section needs to change.

Suggested changeset 1
src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs
--- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs
+++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs
@@ -990,23 +990,25 @@
             try
             {
                 // Create a DataTable for the table-valued parameter
-                var resourceKeysTable = new DataTable();
-                resourceKeysTable.Columns.Add("ResourceTypeId", typeof(short));
-                resourceKeysTable.Columns.Add("ResourceId", typeof(string));
-                resourceKeysTable.Columns.Add("Version", typeof(int));
-
-                // Populate the table with resource keys
-                foreach (var resourceId in resourceIds)
+                using (var resourceKeysTable = new DataTable())
                 {
-                    resourceKeysTable.Rows.Add(resourceTypeId, resourceId, DBNull.Value);
-                }
+                    resourceKeysTable.Columns.Add("ResourceTypeId", typeof(short));
+                    resourceKeysTable.Columns.Add("ResourceId", typeof(string));
+                    resourceKeysTable.Columns.Add("Version", typeof(int));
 
-                // Populate command to use GetResources stored procedure
-                command.CommandType = CommandType.StoredProcedure;
-                command.CommandText = "dbo.GetResources";
-                command.Connection = connection;
+                    // Populate the table with resource keys
+                    foreach (var resourceId in resourceIds)
+                    {
+                        resourceKeysTable.Rows.Add(resourceTypeId, resourceId, DBNull.Value);
+                    }
 
-                var parameter = command.Parameters.AddWithValue("@ResourceKeys", resourceKeysTable);
+                    // Populate command to use GetResources stored procedure
+                    command.CommandType = CommandType.StoredProcedure;
+                    command.CommandText = "dbo.GetResources";
+                    command.Connection = connection;
+
+                    var parameter = command.Parameters.AddWithValue("@ResourceKeys", resourceKeysTable);
+                }
                 parameter.SqlDbType = SqlDbType.Structured;
                 parameter.TypeName = "dbo.ResourceKeyList";
 
EOF
@@ -990,23 +990,25 @@
try
{
// Create a DataTable for the table-valued parameter
var resourceKeysTable = new DataTable();
resourceKeysTable.Columns.Add("ResourceTypeId", typeof(short));
resourceKeysTable.Columns.Add("ResourceId", typeof(string));
resourceKeysTable.Columns.Add("Version", typeof(int));

// Populate the table with resource keys
foreach (var resourceId in resourceIds)
using (var resourceKeysTable = new DataTable())
{
resourceKeysTable.Rows.Add(resourceTypeId, resourceId, DBNull.Value);
}
resourceKeysTable.Columns.Add("ResourceTypeId", typeof(short));
resourceKeysTable.Columns.Add("ResourceId", typeof(string));
resourceKeysTable.Columns.Add("Version", typeof(int));

// Populate command to use GetResources stored procedure
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "dbo.GetResources";
command.Connection = connection;
// Populate the table with resource keys
foreach (var resourceId in resourceIds)
{
resourceKeysTable.Rows.Add(resourceTypeId, resourceId, DBNull.Value);
}

var parameter = command.Parameters.AddWithValue("@ResourceKeys", resourceKeysTable);
// Populate command to use GetResources stored procedure
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "dbo.GetResources";
command.Connection = connection;

var parameter = command.Parameters.AddWithValue("@ResourceKeys", resourceKeysTable);
}
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "dbo.ResourceKeyList";

Copilot is powered by AI and may make mistakes. Always verify output.

// Update to create version 2
patient.Name[0].Given = new[] { "Version2" };
var updateResult = await _fixture.Mediator.UpsertResourceAsync(patient.ToResourceElement(), Core.Features.Persistence.WeakETag.FromVersionId(saveResult.RawResourceElement.VersionId));

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning test

This assignment to
updateResult
is useless, since its value is never read.

Copilot Autofix

AI 2 days ago

In general, when a local variable is assigned a value that is never read, either remove the variable and, if necessary, keep only the side‑effecting expression, or start actually using the variable if the value was intended to be consumed. Here, the test only needs to perform the second upsert to create a new version of the patient resource. The return value (updateResult) is not used in any assertions or subsequent logic.

The best minimal fix that preserves existing behavior is to remove the updateResult variable entirely and just await the upsert call. Concretely, in SqlServerSearchServiceResourceReadOptimizationTests.cs, around line 165, replace:

var updateResult = await _fixture.Mediator.UpsertResourceAsync(patient.ToResourceElement(), Core.Features.Persistence.WeakETag.FromVersionId(saveResult.RawResourceElement.VersionId));

with:

await _fixture.Mediator.UpsertResourceAsync(patient.ToResourceElement(), Core.Features.Persistence.WeakETag.FromVersionId(saveResult.RawResourceElement.VersionId));

No additional imports, methods, or definitions are needed; we are only removing the unused local variable binding while preserving the awaited call and its side effects.

Suggested changeset 1
test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlServerSearchServiceResourceReadOptimizationTests.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlServerSearchServiceResourceReadOptimizationTests.cs b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlServerSearchServiceResourceReadOptimizationTests.cs
--- a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlServerSearchServiceResourceReadOptimizationTests.cs
+++ b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/SqlServerSearchServiceResourceReadOptimizationTests.cs
@@ -162,7 +162,7 @@
 
             // Update to create version 2
             patient.Name[0].Given = new[] { "Version2" };
-            var updateResult = await _fixture.Mediator.UpsertResourceAsync(patient.ToResourceElement(), Core.Features.Persistence.WeakETag.FromVersionId(saveResult.RawResourceElement.VersionId));
+            await _fixture.Mediator.UpsertResourceAsync(patient.ToResourceElement(), Core.Features.Persistence.WeakETag.FromVersionId(saveResult.RawResourceElement.VersionId));
 
             // Act - Search for specific version using history
             var query = new List<Tuple<string, string>>
EOF
@@ -162,7 +162,7 @@

// Update to create version 2
patient.Name[0].Given = new[] { "Version2" };
var updateResult = await _fixture.Mediator.UpsertResourceAsync(patient.ToResourceElement(), Core.Features.Persistence.WeakETag.FromVersionId(saveResult.RawResourceElement.VersionId));
await _fixture.Mediator.UpsertResourceAsync(patient.ToResourceElement(), Core.Features.Persistence.WeakETag.FromVersionId(saveResult.RawResourceElement.VersionId));

// Act - Search for specific version using history
var query = new List<Tuple<string, string>>
Copilot is powered by AI and may make mistakes. Always verify output.
@PTaladay PTaladay closed this Jan 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs Enhancement Enhancement on existing functionality. No-PaaS-breaking-change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants