Fix | Enable transport of large decimal values with explicit Precision and Scale - #4443
Conversation
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: There may be pipelines that require an authorized user to comment /azp run to run. |
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
|
@edwardneal could you please resolve conflicts? |
|
Thanks @cheenamalhotra, I've just merged and resolved. |
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
There was a problem hiding this comment.
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.AdjustDecimalScaleto returnSqlDecimal(notdecimal) and update RPC parameter handling to operate on the SQL type end-to-end. - Remove the now-unneeded
ADP.ParameterValueOutOfRange(decimal)overload and standardize onSqlDecimalfor 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. |
Fixture table column definition change (align with parameter change.) Rename new test. Ensure that connection strings are properly set up.
Head branch was pushed to by a user without write access
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
Codecov Report❌ Patch coverage is
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Description
#1655 indicates that it's impossible to send
decimal.MaxValue(or any other largedecimalvalue) as a parameter to SQL Server when that parameter'sPrecisionandScaleare 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
decimaltype supports a precision of up to 29 significant digits while the SQL Servernumerictype's precision can range up to 38. Any conversion fromSqlDecimal(which aligns with SQL Server) back todecimalafter having been rescaled will risk overflowing - while the value itself might be within the bounds ofdecimal, the precision might not be. TheOverflowExceptionis therefore indicative of a correctness problem within SqlClient, not .NET.This conversion from
SqlDecimaltodecimaloccurs inTdsParser.AdjustDecimalScale. When taken in the context of its call site in (TDSExecuteRPCAddParameter) we see that to adjust the scale of adecimalSqlClient:AdjustDecimalScaledecimalvalueSqlDecimalinstance.SqlDecimal.AdjustScale, accounting for the AppContext switch.SqlDecimal.Valueto converts the scaled value back to adecimal. This is the location of theOverflowException.SqlDecimalwith the return value of the above methodSqlDecimalinstance's precision is >= the desired precision.Once we've rescaled a
decimalvalue, it should never perform a blind round-trip fromSqlDecimaltodecimalwithout checks to ensure that it'll fit in the CLR type. This PR removes this round-trip. We now returnSqlDecimaldirectly fromAdjustDecimalScale, then force the rest ofTDSExecuteRPCAddParameterto 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.