diff --git a/Dapper/DbDecimal.cs b/Dapper/DbDecimal.cs new file mode 100644 index 000000000..5679c6e26 --- /dev/null +++ b/Dapper/DbDecimal.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Dapper +{ + /// + /// This class represents a SQL Decimal (Numeric) type, it can be used if you need + /// to be consistent with query parameter sizes, usually by specifying Precision and Scale + /// values that match the type in the database. + /// + public sealed class DbDecimal : SqlMapper.ICustomQueryParameter + { + private const byte _defaultPrecision = 38; + private const byte _defaultScale = 8; + + /// + /// The value to be inserted or updated in the database + /// + public decimal Value { get; set; } = default; + + /// + /// + /// + public byte Precision { get; set; } = _defaultPrecision; + + /// + /// The number of decimal digits that are stored to the right of the + /// decimal point. + /// + public byte Scale { get; set; } = _defaultScale; + + /// + /// The default constructor used when attaching the individual properties + /// of the parameter. + /// + public DbDecimal() + { } + + /// + /// The primary constructor. This is the constructor that should generally + /// be used to create the object because it accepts values for the properties + /// used in the 90% use-case. + /// + /// The value to be inserted or updated in the database + /// The maximum total number of decimal digits to be stored. + /// The number of decimal digits that are stored to the right of the decimal point. + public DbDecimal(decimal value, byte precision, byte scale) + { + this.Value = value; + this.Precision = precision; + this.Scale = scale; + } + + /// + /// Add the parameter to the command... internal use only + /// + /// + /// + public void AddParameter(IDbCommand command, string name) + { + bool add = !command.Parameters.Contains(name); + + IDbDataParameter param; + if (add) + { + param = command.CreateParameter(); + param.ParameterName = name; + } + else + { + param = (IDbDataParameter)command.Parameters[name]; + } + +#pragma warning disable 0618 + param.Value = SqlMapper.SanitizeParameterValue(Value); +#pragma warning restore 0618 + param.Precision = this.Precision; + param.Scale = this.Scale; + param.DbType = DbType.Decimal; + + if (add) + { + command.Parameters.Add(param); + } + } + + } +} diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 9323be671..99f13e9c6 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -1270,6 +1270,67 @@ public async void SO35470588_WrongValuePidValue() Assert.Equal(568, row.Value); } + [Fact] + public void TestDbDecimal() + { + var obj = connection.Query("select sql_variant_property(@a, 'precision') as a_precision, sql_variant_property(@a, 'scale') as a_scale, " + + "sql_variant_property(@b, 'precision') as b_precision, sql_variant_property(@b, 'scale') as b_scale, " + + "sql_variant_property(@c, 'precision') as c_precision, sql_variant_property(@c, 'scale') as c_scale, " + + "sql_variant_property(@d, 'precision') as d_precision, sql_variant_property(@d, 'scale') as d_scale, " + + "sql_variant_property(@e, 'precision') as e_precision, sql_variant_property(@e, 'scale') as e_scale", + new + { + a = new DbDecimal { Value = 123456.78M, Precision = 9, Scale = 3 }, + b = new DbDecimal { Value = 123456.78M, Precision = 18, Scale = 3 }, + c = new DbDecimal { Value = 123456.78M, Precision = 27, Scale = 3 }, + d = new DbDecimal { Value = 123456.78M, Precision = 36, Scale = 3 }, + e = new DbDecimal { Value = 123456.78M }, + }).First(); + + Assert.Equal(3, (int)obj.a_scale); + Assert.Equal(9, (int)obj.a_precision); + + Assert.Equal(3, (int)obj.b_scale); + Assert.Equal(18, (int)obj.b_precision); + + Assert.Equal(3, (int)obj.c_scale); + Assert.Equal(27, (int)obj.c_precision); + + Assert.Equal(3, (int)obj.d_scale); + Assert.Equal(36, (int)obj.d_precision); + + Assert.Equal(8, (int)obj.e_scale); + Assert.Equal(38, (int)obj.e_precision); + } + + [Fact] + public void TestDbDecimalUsingParameterizedConstructor() + { + var obj = connection.Query("select sql_variant_property(@a, 'precision') as a_precision, sql_variant_property(@a, 'scale') as a_scale, " + + "sql_variant_property(@b, 'precision') as b_precision, sql_variant_property(@b, 'scale') as b_scale, " + + "sql_variant_property(@c, 'precision') as c_precision, sql_variant_property(@c, 'scale') as c_scale, " + + "sql_variant_property(@d, 'precision') as d_precision, sql_variant_property(@d, 'scale') as d_scale", + new + { + a = new DbDecimal(123456.78M, 9, 3), + b = new DbDecimal(123456.78M, 18, 3), + c = new DbDecimal(123456.78M, 27, 3), + d = new DbDecimal(123456.78M, 36, 3), + }).First(); + + Assert.Equal(3, (int)obj.a_scale); + Assert.Equal(9, (int)obj.a_precision); + + Assert.Equal(3, (int)obj.b_scale); + Assert.Equal(18, (int)obj.b_precision); + + Assert.Equal(3, (int)obj.c_scale); + Assert.Equal(27, (int)obj.c_precision); + + Assert.Equal(3, (int)obj.d_scale); + Assert.Equal(36, (int)obj.d_precision); + } + public class TPTable { public int Pid { get; set; }