question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Decimal parameters are truncated when precision and scale are set

See original GitHub issue

This is the issue we discussed at the last EF/SqlClient sync. It is related to https://github.com/dotnet/efcore/issues/19293


Consider attempting to insert the value 10.9876 into a decimal(18, 2) column.

  • If DbParameter.Precision is set to 18, and DbParameter.Scale is set to 2, then
    • SqlClient truncates the value to 10.98 before inserting.
  • If DbParameter.Precision and DbParameter.Scale are not set, then:
    • SqlClient passes the full 10.9876 to SQL Server
    • SQL Server rounds this to 10.99.

Historically the behavior in SqlClient could not be changed, since it would be a break for existing applications that are relying on truncation.

At the same time, EF didn’t set precision and scale on decimal parameters (since before my time on the team!) which meant EF was relying on SQL Server rounding.

Initially in EF Core we set precision and scale, but people argued that truncation was a deviation from EF6 behavior and that rounding was better. In fact, a reasonable case could be made that the truncation behavior was wrong. We concurred and so we kept the rounding behavior in EF Core.


Enter always-encrypted. Now not setting precision and scale on a decimal parameter causes SQL Server to throw. See https://github.com/dotnet/efcore/issues/19293.

But if EF starts setting precision and scale, then the behavior will change to truncation.

So, options here are:

  • SqlClient introduces a rounding behavior. Note that this is breaking if it is on by default.
  • EF introduces its own rounding behavior before passing the value to SqlClient
  • We start throwing and require applications truncate/round as appropriate. This is also breaking.

After writing this, I’m leaning even more towards EF doing rounding. But we should first be sure that SqlClient doesn’t want to change or introduce new behaviors.


Repro code. The code uses EF only to create a database and print results. The inserts use SqlClient directly.

public static class ThreeOne
{
    public static void Main()
    {
        // Use use EF to create a test database
        using (var context = new SomeDbContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();
        }

        InsertRow(1, 10.9876m, null);
        InsertRow(2, 10.9876m, (18, 2));

        // Use use EF to easily read the inserted values back
        using (var context = new SomeDbContext())
        {
            var products = context.Products.ToList();
            Console.WriteLine($"Product {products[0].Id} has decimal value {products[0].Price}");
            Console.WriteLine($"Product {products[1].Id} has decimal value {products[1].Price}");
        }

        void InsertRow(int id, decimal price, (byte, byte)? precisionAndScale)
        {
            using var connection = new SqlConnection(Your.SqlServerConnectionString);
            using var command = connection.CreateCommand();

            var idParameter = command.CreateParameter();
            idParameter.ParameterName = "p0";
            idParameter.Value = id;

            var priceParameter = command.CreateParameter();
            priceParameter.ParameterName = "p1";
            priceParameter.DbType = DbType.Decimal;

            if (precisionAndScale.HasValue)
            {
                var (precision, scale) = precisionAndScale.Value;
                priceParameter.Precision = precision;
                priceParameter.Scale = scale;
            }

            priceParameter.Value = price;

            command.Parameters.Add(idParameter);
            command.Parameters.Add(priceParameter);
            command.CommandText = "INSERT INTO [Products] ([Id], [Price]) VALUES (@p0, @p1)";
            connection.Open();
            command.ExecuteNonQuery();
            connection.Close();
        }
    }
}

public class SomeDbContext : DbContext
{
    private static readonly ILoggerFactory
        Logger = LoggerFactory.Create(x => x.AddConsole()); //.SetMinimumLevel(LogLevel.Debug));

    public DbSet<Product> Products { get; set; }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            //.UseLoggerFactory(Logger)
            .EnableSensitiveDataLogging()
            .UseSqlServer(Your.SqlServerConnectionString);
}

public class Product
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }
    
    [Column(TypeName = "decimal(18,2)")]
    public decimal Price { get; set; }
}

/cc @AndriySvyryd @bricelam @roji

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

3reactions
cheenamalhotracommented, Feb 24, 2020

Hi @ajcvickers @roji

SqlClient introduces a rounding behavior. Note that this is breaking if it is on by default.

I agree on SqlClient making this change in order to provide consistent behavior that is expected from a SQL Server client driver to match SQL Server behavior.

  • Will it be a breaking change? Yes. 2.0 brings this opportunity to set things straight.
  • Will there be workaround? Yes. We can provide a toggle behavior for applications willing to continue to truncate for backwards compatibility.
  • Does EF Core need to do something? No. Since this is a driver issue, wrappers/frameworks need not handle this change specifically.

Doing so should address all cases in applications, also setting things right for the driver.

/cc: @saurabh500 @David-Engel

0reactions
ajcvickerscommented, Feb 24, 2020

@cheenamalhotra Excellent!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Precision, scale, and length (Transact-SQL) - SQL Server
In SQL Server, the default maximum precision of numeric and decimal data types is 38. Length for a numeric data type is the...
Read more >
Set decimal precision for query result object
This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL...
Read more >
This will cause values to be silently truncated if they do not ...
Code first warning: This will cause values to be silently truncated if they do not fit in the default precision and scale.Closed -...
Read more >
DECIMAL or DEC scalar function
Digits are truncated from the end of the decimal number if the number of digits to the right of the decimal separator character...
Read more >
11.1.3 Fixed-Point Types (Exact Value) - DECIMAL, ...
The precision represents the number of significant digits that are stored for values, and the scale represents the number of digits that can...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found