diff --git a/build.ps1 b/build.ps1 index add4cf0..a013173 100644 --- a/build.ps1 +++ b/build.ps1 @@ -11,4 +11,4 @@ if ($connectionstring) dotnet restore .\src\Dapper.Oracle.sln dotnet build .\src\Dapper.Oracle.sln dotnet test .\src\Dapper.Oracle.sln -dotnet pack .\src\Dapper.Oracle.sln -p:PackageVersion=2.0.4 \ No newline at end of file +dotnet pack .\src\Dapper.Oracle.sln -p:PackageVersion=2.1.0 diff --git a/src/Dapper.Oracle.StrongName/Dapper.Oracle.StrongName.csproj b/src/Dapper.Oracle.StrongName/Dapper.Oracle.StrongName.csproj index 3bdda7a..ee76527 100644 --- a/src/Dapper.Oracle.StrongName/Dapper.Oracle.StrongName.csproj +++ b/src/Dapper.Oracle.StrongName/Dapper.Oracle.StrongName.csproj @@ -1,9 +1,9 @@ - + false - net7.0 + net10.0 true key.snk @@ -13,10 +13,10 @@ - + - + - \ No newline at end of file + diff --git a/src/Dapper.Oracle/Dapper.Oracle.csproj b/src/Dapper.Oracle/Dapper.Oracle.csproj index 305e81d..0289057 100644 --- a/src/Dapper.Oracle/Dapper.Oracle.csproj +++ b/src/Dapper.Oracle/Dapper.Oracle.csproj @@ -1,26 +1,28 @@ - + false - net7.0 + net10.0 -Version 2.0.4: - - Updated to use .net 7.0. - - Dependency switched to most recent version of Dapper: 2.0.123. - - Dependencies updated also for test project. - + Version 2.1.0: + - Updated to use .net 10.0. + - Dependency switched to most recent version of Dapper: 2.1.72. + - Dependencies updated also for test project. + - Fix OracleDecimal to System.Decimal conversion overflow handling + - Enriching OracleParameterInfo with flag for PII data + - + - + - \ No newline at end of file + diff --git a/src/Dapper.Oracle/OracleDecimalConversion.cs b/src/Dapper.Oracle/OracleDecimalConversion.cs new file mode 100644 index 0000000..10c1226 --- /dev/null +++ b/src/Dapper.Oracle/OracleDecimalConversion.cs @@ -0,0 +1,17 @@ +namespace Dapper.Oracle +{ + /// + /// Maps Oracle native numeric values (especially OracleDecimal) to , + /// using OracleDecimal.SetPrecision(..., 28) before reading System.Decimal (same idea as legacy ADO.NET). + /// + /// + /// Used internally by (for example from ). + /// Dapper's default Query<decimal> / ExecuteScalar<decimal> does not call this; for result sets use this helper on raw values, SQL CAST, or a custom TypeHandler. + /// + public static class OracleDecimalConversion + { + public static decimal ToDecimal(object value) => OracleValueConverter.Convert(value); + + public static decimal? ToDecimalNullable(object value) => OracleValueConverter.Convert(value); + } +} diff --git a/src/Dapper.Oracle/OracleDynamicParameters.cs b/src/Dapper.Oracle/OracleDynamicParameters.cs index cfb6bc5..10c7029 100644 --- a/src/Dapper.Oracle/OracleDynamicParameters.cs +++ b/src/Dapper.Oracle/OracleDynamicParameters.cs @@ -1,4 +1,4 @@ -//// Based on Gist found here: https://gist.github.com/vijaysg/3096151 +//// Based on Gist found here: https://gist.github.com/vijaysg/3096151 using System; using System.Collections; @@ -109,6 +109,7 @@ public void AddDynamicParams(dynamic param) /// /// /// + /// a flag that this param contains sensitive data and it must be masked in case of logging values public void Add( string name, object value = null, @@ -121,7 +122,8 @@ public void Add( string sourceColumn = null, DataRowVersion? sourceVersion = null, OracleMappingCollectionType? collectionType = null, - int[] arrayBindSize = null) + int[] arrayBindSize = null, + bool maskValueWhenLogging = false) { Parameters[Clean(name)] = new OracleParameterInfo() { @@ -136,7 +138,8 @@ public void Add( SourceColumn = sourceColumn, SourceVersion = sourceVersion ?? DataRowVersion.Current, CollectionType = collectionType ?? OracleMappingCollectionType.None, - ArrayBindSize = arrayBindSize + ArrayBindSize = arrayBindSize, + MaskValueWhenLogging = maskValueWhenLogging }; } @@ -167,7 +170,7 @@ public T Get(string name) } return default(T); } - + return OracleValueConverter.Convert(val); } @@ -239,10 +242,10 @@ protected virtual void AddParameters(IDbCommand command, SqlMapper.Identity iden } OracleMethodHelper.SetOracleParameters(p, param); - + p.Direction = param.ParameterDirection; - - var val = param.Value; + + var val = param.Value; if (val != null && OracleTypeMapper.HasTypeHandler(val.GetType(), out var handler)) { @@ -251,7 +254,7 @@ protected virtual void AddParameters(IDbCommand command, SqlMapper.Identity iden else { p.Value = val ?? DBNull.Value; - + var s = val as string; if (s?.Length <= 4000) { @@ -262,8 +265,8 @@ protected virtual void AddParameters(IDbCommand command, SqlMapper.Identity iden { p.Size = param.Size.Value; } - } - + } + if (add) { command.Parameters.Add(p); @@ -320,6 +323,8 @@ public class OracleParameterInfo public OracleParameterMappingStatus Status { get; set; } public IDbDataParameter AttachedParam { get; set; } + + public bool MaskValueWhenLogging { get; set; } } /// @@ -328,4 +333,4 @@ public class OracleParameterInfo /// public IEnumerator GetEnumerator() => Parameters.GetEnumerator(); } -} \ No newline at end of file +} diff --git a/src/Dapper.Oracle/OracleValueConverter.cs b/src/Dapper.Oracle/OracleValueConverter.cs index fe1f579..8aa9719 100644 --- a/src/Dapper.Oracle/OracleValueConverter.cs +++ b/src/Dapper.Oracle/OracleValueConverter.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Data.Common; using System.Linq; +using System.Reflection; using System.Reflection.Emit; using System.Text.RegularExpressions; @@ -126,7 +127,10 @@ private static T ConvertArray(object value) var decimalArray = new decimal[arr.Length]; for (int i = 0; i < arr.Length; i++) { - decimalArray[i] = decimal.Parse(arr.GetValue(i)?.ToString()); + var raw = arr.GetValue(i); + decimalArray[i] = TryReduceOracleDecimalToSystemDecimal(raw, out var systemDecimal) + ? systemDecimal + : decimal.Parse(raw?.ToString() ?? "0"); } return (T)System.Convert.ChangeType(decimalArray, nullableType ?? typeof(T)); case "System.Boolean[]": @@ -187,6 +191,12 @@ private static object GetValue(object value) return null; } + // Legacy ADO.NET: OracleDecimal.SetPrecision(..., 28).Value — no compile-time reference to Oracle.ManagedDataAccess. + if (TryReduceOracleDecimalToSystemDecimal(value, out var asDecimal)) + { + return asDecimal; + } + var val = valueType.GetProperty("Value")?.GetValue(value); if (val != null) { @@ -208,5 +218,45 @@ private static bool IsOracleDataStructure(Type valueType) // See: https://docs.oracle.com/en/database/oracle/oracle-database/19/odpnt/intro003.html#GUID-425C9EBA-CFFC-47FE-B490-604251714ACA return Regex.IsMatch(valueType.FullName, @"Oracle\.\w+\.Types\.Oracle\w+"); } + + private const string OracleDecimalTypeFullName = "Oracle.ManagedDataAccess.Types.OracleDecimal"; + + private static bool IsOracleDecimal(Type type) => + type != null && type.FullName == OracleDecimalTypeFullName; + + /// + /// Calls OracleDecimal.SetPrecision(instance, 28).Value via reflection when ODP.NET is loaded at runtime. + /// + private static bool TryReduceOracleDecimalToSystemDecimal(object value, out decimal systemDecimal) + { + systemDecimal = default; + if (value == null || !IsOracleDecimal(value.GetType())) + { + return false; + } + + var t = value.GetType(); + var setPrecision = t.GetMethod( + "SetPrecision", + BindingFlags.Public | BindingFlags.Static, + binder: null, + types: new[] { t, typeof(int) }, + modifiers: null); + if (setPrecision == null) + { + return false; + } + + var reduced = setPrecision.Invoke(null, new object[] { value, 28 }); + var valueProp = t.GetProperty("Value"); + var boxed = valueProp?.GetValue(reduced); + if (boxed is decimal d) + { + systemDecimal = d; + return true; + } + + return false; + } } } diff --git a/src/Tests.Dapper.Oracle/IntegrationTests/DatabaseFixture.cs b/src/Tests.Dapper.Oracle/IntegrationTests/DatabaseFixture.cs index 80c64c6..f3477ce 100644 --- a/src/Tests.Dapper.Oracle/IntegrationTests/DatabaseFixture.cs +++ b/src/Tests.Dapper.Oracle/IntegrationTests/DatabaseFixture.cs @@ -35,10 +35,9 @@ public void Dispose() public async Task InitializeAsync() { + // Prefer DA_OR_CONNECTION (CI / local Oracle). Do not overwrite it — a hardcoded default used to break env-based runs. var connectionString = Environment.GetEnvironmentVariable("DA_OR_CONNECTION"); - connectionString = "Data Source=localhost/dips;User Id=system;Password=oracle"; - if (string.IsNullOrEmpty(connectionString)) { var si = new ProcessStartInfo("powershell", @".\LocalOracleDockerDb.ps1"); @@ -100,4 +99,4 @@ private static string GetBootstrapFolder() } } -} \ No newline at end of file +} diff --git a/src/Tests.Dapper.Oracle/OracleValueConverterTests.cs b/src/Tests.Dapper.Oracle/OracleValueConverterTests.cs index dcc0ef5..b3ba639 100644 --- a/src/Tests.Dapper.Oracle/OracleValueConverterTests.cs +++ b/src/Tests.Dapper.Oracle/OracleValueConverterTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using Dapper.Oracle; using FluentAssertions; @@ -249,6 +249,22 @@ public void GetOracleDecimalReturnsDecimal() result.Should().Be(expected); } + [Fact] + public void GetOracleDecimalMatchesExplicitSetPrecision28() + { + foreach (var d in new[] + { + 0m, + 1m, + 0.4690679611650485436893203883m, + }) + { + var od = new OracleDecimal(d); + var expected = OracleDecimal.SetPrecision(od, 28).Value; + OracleValueConverter.Convert(od).Should().Be(expected); + } + } + [Fact] public void GetOracleDecimalReturnsNullableDecimal() { diff --git a/src/Tests.Dapper.Oracle/Tests.Dapper.Oracle.csproj b/src/Tests.Dapper.Oracle/Tests.Dapper.Oracle.csproj index af10b18..bf51727 100644 --- a/src/Tests.Dapper.Oracle/Tests.Dapper.Oracle.csproj +++ b/src/Tests.Dapper.Oracle/Tests.Dapper.Oracle.csproj @@ -1,22 +1,27 @@ - + true - net7.0 + net10.0 false + + false + + + Category!=Integration&Category!=IntegrationTest - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -36,7 +41,7 @@ - + - \ No newline at end of file +