Skip to content

Fix | Enable transport of large decimal values with explicit Precision and Scale - #4443

Merged
paulmedynski merged 3 commits into
dotnet:mainfrom
edwardneal:fix/issue-1655
Jul 29, 2026
Merged

Fix | Enable transport of large decimal values with explicit Precision and Scale#4443
paulmedynski merged 3 commits into
dotnet:mainfrom
edwardneal:fix/issue-1655

Conversation

@edwardneal

Copy link
Copy Markdown
Contributor

Description

#1655 indicates that it's impossible to send decimal.MaxValue (or any other large decimal value) as a parameter to SQL Server when that parameter's Precision and Scale are set. This is a challenge for Always Encrypted scenarios, which requires these to always be explicitly set.

This issue was originally marked as external to SqlClient, but upon review I don't believe this is the case. The root cause is that the .NET decimal type supports a precision of up to 29 significant digits while the SQL Server numeric type's precision can range up to 38. Any conversion from SqlDecimal (which aligns with SQL Server) back to decimal after having been rescaled will risk overflowing - while the value itself might be within the bounds of decimal, the precision might not be. The OverflowException is therefore indicative of a correctness problem within SqlClient, not .NET.

This conversion from SqlDecimal to decimal occurs in TdsParser.AdjustDecimalScale. When taken in the context of its call site in (TDSExecuteRPCAddParameter) we see that to adjust the scale of a decimal SqlClient:

  1. Calls AdjustDecimalScale
    1. Gets the current scale of the decimal value
    2. Checks whether the value needs to be rescaled (i.e. whether its current scale does not already match the desired scale). If so:
      1. Instantiates a new SqlDecimal instance.
      2. Calls SqlDecimal.AdjustScale, accounting for the AppContext switch.
      3. Returns SqlDecimal.Value to converts the scaled value back to a decimal. This is the location of the OverflowException.
  2. Instantiates a new SqlDecimal with the return value of the above method
  3. Verifies that the new SqlDecimal instance's precision is >= the desired precision.

Once we've rescaled a decimal value, it should never perform a blind round-trip from SqlDecimal to decimal without checks to ensure that it'll fit in the CLR type. This PR removes this round-trip. We now return SqlDecimal directly from AdjustDecimalScale, then force the rest of TDSExecuteRPCAddParameter to treat the value as a SQL type. This has the pleasant side-effect of being able to merge some of the verification logic.

Issues

Fixes #1655.
Upstream EF Core issues: dotnet/efcore#28240; dotnet/efcore#33109; dotnet/efcore#35921.

Testing

One new test added which exercises the code path. One Always Encrypted test modified to verify that there have been no regressions.

This is necessary because a normal System.Decimal value is limited to a precision of 29, while SQL Server's numeric type has a maximum precision of 38.
@azure-pipelines

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

@cheenamalhotra

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

@paulmedynski paulmedynski moved this from To triage to Backlog in SqlClient Board Jul 16, 2026
@cheenamalhotra cheenamalhotra added this to the 7.1.0-preview3 milestone Jul 20, 2026
@cheenamalhotra cheenamalhotra moved this from Backlog to In review in SqlClient Board Jul 20, 2026
@cheenamalhotra

Copy link
Copy Markdown
Member

@edwardneal could you please resolve conflicts?
Thanks!

@edwardneal

Copy link
Copy Markdown
Contributor Author

Thanks @cheenamalhotra, I've just merged and resolved.

@paulmedynski

Copy link
Copy Markdown
Contributor

/azp run

@paulmedynski
paulmedynski enabled auto-merge (squash) July 27, 2026 17:25
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

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.

Pull request overview

This PR fixes sending large decimal values (e.g., decimal.MaxValue) as SQL parameters when SqlParameter.Precision/Scale are explicitly set by removing an unsafe SqlDecimal -> decimal round-trip during scale adjustment. This is particularly important for Always Encrypted scenarios where precision/scale must be specified.

Changes:

  • Update TdsParser.AdjustDecimalScale to return SqlDecimal (not decimal) and update RPC parameter handling to operate on the SQL type end-to-end.
  • Remove the now-unneeded ADP.ParameterValueOutOfRange(decimal) overload and standardize on SqlDecimal for range reporting.
  • Add a manual test covering the regression and adjust an Always Encrypted manual test to use precision 38.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs Avoids overflowing CLR decimal during scale adjustment by keeping values as SqlDecimal through parameter serialization.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs Removes the obsolete ParameterValueOutOfRange(decimal) overload now that range checks use SqlDecimal.
src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs Adds a regression test that round-trips a high-precision/scale decimal via SqlDecimal.
src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs Updates AE test to use precision 38 to exercise numeric(38,*) parameter handling.

paulmedynski
paulmedynski previously approved these changes Jul 27, 2026
mdaigle
mdaigle previously approved these changes Jul 28, 2026
Fixture table column definition change (align with parameter change.)
Rename new test.
Ensure that connection strings are properly set up.
auto-merge was automatically disabled July 29, 2026 05:25

Head branch was pushed to by a user without write access

@edwardneal
edwardneal dismissed stale reviews from mdaigle and paulmedynski via 1c525ad July 29, 2026 05:25
@apoorvdeshmukh

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 62.77%. Comparing base (fdebcd2) to head (1c525ad).
⚠️ Report is 37 commits behind head on main.

Files with missing lines Patch % Lines
...qlClient/src/Microsoft/Data/SqlClient/TdsParser.cs 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4443      +/-   ##
==========================================
- Coverage   65.83%   62.77%   -3.07%     
==========================================
  Files         287      283       -4     
  Lines       43763    66979   +23216     
==========================================
+ Hits        28812    42043   +13231     
- Misses      14951    24936    +9985     
Flag Coverage Δ
CI-SqlClient ?
PR-SqlClient-Project 62.77% <90.90%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@paulmedynski
paulmedynski merged commit 45f4759 into dotnet:main Jul 29, 2026
360 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in SqlClient Board Jul 29, 2026
@edwardneal
edwardneal deleted the fix/issue-1655 branch July 29, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Cannot write decimal.MaxValue if precision/scale are set on SqlParameter

6 participants