Skip to content

Add an EnableAppConfig switch to remove trim warnings - #4697

Open
charlesroddie wants to merge 4 commits into
dotnet:mainfrom
charlesroddie:trim-app-config-switch
Open

charlesroddie wants to merge 4 commits into
dotnet:mainfrom
charlesroddie:trim-app-config-switch

Conversation

@charlesroddie

@charlesroddie charlesroddie commented Sep 16, 2026

Copy link
Copy Markdown

Part of the work toward #1947.

Problem

On .NET, SqlClient reads app.config from three places:

  • SqlConfigurableRetryLogicManager, for the retry sections. Reached from SqlConnection.Open and every SqlCommand.Execute*, which read RetryLogicProvider unconditionally.
  • LocalAppContextSwitches' static constructor, for the AppContextSwitchOverrides section.
  • SqlAuthenticationProviderManager's static constructor, for the auth provider sections.

ConfigurationManager.GetSection resolves section handler types named as strings in the config file, so reaching it from anywhere leaves five trim warnings inside System.Configuration.TypeUtil (IL2026, two IL2057, IL2067, IL2070). They are in the dependency rather than in SqlClient, so no annotation here can remove them, and they are a fixed cost rather than a per-call-site one: a single ungated caller keeps all five. dotnet/runtime#49062 is closed with no fix planned, and upstream's guidance is to use Microsoft.Extensions.Configuration instead, which is what the @TODO comments on both FetchConfigurationSection copies already anticipate.

Annotating public entry points with RequiresUnreferencedCode is not an option for the retry path, because it is reached from Open and Execute* rather than from retry-specific API, so the annotation would propagate onto every caller of those methods.

Change

A new Switch.Microsoft.Data.SqlClient.EnableAppConfig, defaulting to true, with a guard at each app.config reader, including the .NET Framework system.data.localdb one. When the switch is fixed at publish time, ILLink.Substitutions.xml makes the trimmer treat the property as a constant, as it already does for UseManagedNetworking, so the configuration reading becomes dead code and is removed.

The retry guards sit at the SqlCommand and SqlConnection call sites rather than inside SqlConfigurableRetryLogicManager, because that class builds its loader in a static field initializer, so touching the type at all would run the configuration reading regardless of the guard.

Measurement

Native AOT publish of a small ASP.NET Core application that opens a connection, runs a stored procedure with a DataTable-valued parameter inside a transaction, reads results, and uses Azure Monitor OpenTelemetry, with TrimmerSingleWarn=false. Measured on top of main plus #4683, #4684 and #4688, counting only warnings the application can reach:

SqlClient System.Configuration total
Before 24 5 29
Publishing with the switch set to false 19 0 19

The ten removed are the five in System.Configuration.TypeUtil and the five in SqlConfigurableRetryLogicLoader.

Behaviour

Default true keeps today's behaviour exactly. Set to false, app.config is ignored: no configurable retry logic from config, no config-declared authentication providers, no AppContextSwitchOverrides, and on .NET Framework no system.data.localdb instances. This is a real behaviour change rather than a pure trimming knob, so it is opt-in per application.

The switch is read before any override is applied, so it cannot itself be set from the app.config that it gates. It has to be set through AppContext or runtimeconfig.

Open question

features.instructions.md says new switches should default to false and opt in to new behaviour. This one gates behaviour that already exists, and the default has to mean "the feature is present" for the substitution to trim the disabled path, so it defaults to true. Happy to rename it to DisableAppConfig if you would rather follow the guideline literally, though that makes the substitution entries read backwards.

🤖 Generated with Claude Code

On .NET, SqlClient reads app.config from three places: the configurable
retry logic manager, LocalAppContextSwitches' own static constructor, and
SqlAuthenticationProviderManager.  ConfigurationManager.GetSection resolves
section handler types named as strings in the config file, so reaching it
from anywhere produces five trim warnings inside System.Configuration's
TypeUtil that no annotation in SqlClient can remove.  dotnet/runtime#49062
is closed with no fix planned.

Gate all three readers behind a new EnableAppConfig switch, defaulting to
true, and stub it through ILLink.Substitutions.xml as UseManagedNetworking
already is.  Publishing with the switch set to false removes those five
warnings and the five in SqlConfigurableRetryLogicLoader, measured on a
Native AOT application.

The retry guards sit at the SqlCommand and SqlConnection call sites rather
than inside SqlConfigurableRetryLogicManager, whose static field
initializer would otherwise still run and keep the configuration reading
reachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings September 16, 2026 09:06
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Sep 16, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The disabled path introduces per-command and per-connection allocations and lacks behavioral coverage for the gated readers.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds an opt-out switch for app.config loading to reduce trimming and Native AOT warnings while preserving existing behavior by default.

Changes:

  • Adds and documents EnableAppConfig.
  • Guards retry, authentication, and switch-override configuration reads.
  • Adds linker substitutions and default-value test support.
File summaries
File Description
.github/instructions/features.instructions.md Documents the switch.
LocalAppContextSwitches.cs Defines and applies the switch.
SqlAuthenticationProviderManager.cs Gates authentication configuration.
SqlCommand.cs Gates command retry configuration.
SqlConnection.cs Gates connection retry configuration.
ILLink.Substitutions.xml Enables publish-time substitution.
LocalAppContextSwitchesHelper.cs Adds test access to the switch.
LocalAppContextSwitchesTest.cs Verifies the default value.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs Outdated
Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs Outdated
charlesroddie and others added 2 commits September 16, 2026 10:29
With EnableAppConfig off, each command and connection created its own
no-retry provider, where the manager shares one of each process-wide.
Keep a lazily created shared provider for each instead, without touching
SqlConfigurableRetryLogicManager.

Add tests that the switch selects between the shared providers and the
manager's. The authentication provider and switch override readers run in
static constructors, so they cannot be tested in-process.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On .NET Framework, LocalDbApi still read system.data.localdb from
app.config with the switch off, so the switch did not mean the same thing
on every platform.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 16, 2026 11:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The documentation must distinguish runtime switch behavior from the publish-time configuration required for trimming.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread .github/instructions/features.instructions.md Outdated
Also list the .NET Framework system.data.localdb section among what the
switch gates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 16, 2026 11:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The switch consistently gates all identified configuration readers, preserves default behavior, and includes focused documentation and tests.

Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@charlesroddie charlesroddie changed the title Add EnableAppConfig switch to remove System.Configuration trim warnings Add an EnableAppConfig switch to remove trim warnings Sep 16, 2026
@charlesroddie
charlesroddie marked this pull request as ready for review September 16, 2026 18:19
@charlesroddie
charlesroddie requested a review from a team September 16, 2026 18:19
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Comment on lines -47 to +46
// If configuration section is not yet found, try with old Configuration Section name for backwards compatibility
configurationSection = FetchConfigurationSection<SqlAuthenticationProviderConfigurationSection>(SqlAuthenticationProviderConfigurationSection.Name);
// New configuration section "SqlClientAuthenticationProviders" for Microsoft.Data.SqlClient accepted to avoid conflicts with older one.
configurationSection = FetchConfigurationSection<SqlClientAuthenticationProviderConfigurationSection>(SqlClientAuthenticationProviderConfigurationSection.Name);

@edwardneal edwardneal Sep 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will remove the trim warning - does it allow ILLink to trim away every type within the System.Configuration assembly? We can use sizoscope to check this, it'll remove around 1.4MB if so.

@charlesroddie charlesroddie Sep 16, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not every type but most. System.Configuration.ConfigurationManager.dll goes from 443KB untrimmed, to 133KB trimmed before this PR (i.e. with the switch on), to 42KB trimmed with the switch off.

The residual is to do with SqlAuthenticationProviderManager where is ongoing work.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without loading data from app.config, the constructor for SqlAuthenticationProviderManager has no work to do. One way to remove this might be to have a private parameterless constructor which no-ops or logs; the class could be instantiated with that if the EnableAppConfig switch is disabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: To triage

Development

Successfully merging this pull request may close these issues.

6 participants