尽管设置了精度,但 MSSQL table 中的十进制数会四舍五入

Decimal number in MSSQL table gets rounded although precision is set

我的 c# 对象有一个小数 属性:

public decimal LastPrice { get; set; }

在处理我的对象时,设置了十进制值。例如:

LastPrice = 0.091354;

我修改了 DbContext 以提高小数精度,如另一个 Whosebug post:

中所述
protected override void OnModelCreating(ModelBuilder modelBuilder)
{       
        foreach (var property in modelBuilder.Model.GetEntityTypes()
            .SelectMany(t => t.GetProperties())
            .Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
        {
            property.SetColumnType("decimal(38, 10)");
        }        
}

Microsoft Sql Server Management Studio 中的 tables 设计视图反映了此配置。 这是从 SSMS 编写的 table 脚本:

CREATE TABLE [dbo].[TradedCurrencyPairs](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [LastPrice] [decimal](38, 10) NOT NULL,
...

当我在调试期间检查添加到我的 DbContext 的对象时,它看起来不错:

但在数据库中它最终为 0.0000000000。

据我所知,该值仍在四舍五入,就好像它的精度为 2。本应为 0.09232 的值变为 0.0900000000。 所以所有小数点仍然被削减。

我尝试了几种不同的数据注释:

// [PrecisionAndScale(20, 10)]
//[RegularExpression(@"^\d+\.\d{20,10}$")]
//[Range(0, 99999999999999999999.9999999999)]
[Range(typeof(decimal), "20", "10")]

但他们没有帮助。 从 SSMS 插入数据工作正常: 插入交易货币对 VALUES ('tzt', 'ttt', 'rrrr', '20120618 10:34:09 AM', 'hgghghg', 0.123456, 1, 0.123456789, 0.123456, 0.123456); 去

我的列 DbModelSnapshot 如下所示:

            b.Property<decimal>("LastPrice")
                .HasPrecision(10)
                .HasColumnType("decimal(20, 10)");

我也试过:

TradedCurrencyPair TestPair = new TradedCurrencyPair("one", "two", "Bibox", DateTime.Now, "unknown", 0.1234567890M, 1, 0.1234567890M, 0.1234567890M, 0.1234567890M);
                context.TradedCurrencyPairs.Add(TestPair);
                context.SaveChanges(); 

结果是一样的...

在设置值和最终存入数据库之间的某处,它被修改了:/

这里是 SQL Table:

/****** Object:  Table [dbo].[TradedCurrencyPairs]    Script Date: 25/07/2020 09:32:32 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[TradedCurrencyPairs](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [TradedCurrency] [nvarchar](max) NULL,
    [BaseCurrency] [nvarchar](max) NULL,
    [Exchange] [nvarchar](max) NULL,
    [DateTime] [datetime2](7) NOT NULL,
    [TransactionType] [nvarchar](max) NULL,
    [LastPrice] [decimal](20, 10) NOT NULL,
    [ExchangeInternalPairId] [int] NOT NULL,
    [High24h] [decimal](20, 10) NOT NULL,
    [Low24h] [decimal](20, 10) NOT NULL,
    [Volume24h] [decimal](20, 10) NOT NULL,
 CONSTRAINT [PK_TradedCurrencyPairs] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

没有使用 entity framework:

SqlCommand command = new SqlCommand("insert into TradedCurrencyPairs values('one', 'two', 'Bibox', convert(datetime, '18-06-12 10:34:09 PM', 5), 'unknown', 0.1234567890, 1, 0.1234567890, 0.1234567890, 0.1234567890); ", cnn);
This way the decimals do not get modified. So EF causes the issue.

任何人都可以向我解释我做错了什么吗?

非常感谢!

EF core 5 支持模型构建器的精度尝试:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<TradedCurrencyPair>()
        .Property(to => tp.LastPrice)
        .HasPrecision(38, 10);
}

或者您可以像使用 SetColumnType 一样使用 SetPrecision,请参阅 github

您的代码(post在此处编辑)通常应该可以工作。这是一个快速示例程序,显示了这一点:

using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate
{
    public class IceCream
    {
        public int IceCreamId { get; set; }
        public string Name { get; set; }
        public decimal PricePerKilogram { get; set; }
    }

    public class Context : DbContext
    {
        public DbSet<IceCream> IceCreams { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(@"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63079237")
                .UseLoggerFactory(
                    LoggerFactory.Create(
                        b => b
                            .AddConsole()
                            .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<IceCream>(
                entity =>
                {
                    entity.Property(e => e.PricePerKilogram)
                        .HasColumnType("decimal(38, 10)");
                    
                    entity.HasData(
                        new IceCream {IceCreamId = 1, Name = "Vanilla", PricePerKilogram = 123456789.123456789M},
                        new IceCream {IceCreamId = 2, Name = "Chocolate", PricePerKilogram = 0.0000001234M});
                });
        }
    }

    internal static class Program
    {
        private static void Main()
        {
            using var context = new Context();

            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            var iceCreams = context.IceCreams
                .OrderBy(i => i.IceCreamId)
                .ToList();
            
            Debug.Assert(iceCreams.Count == 2);
            Debug.Assert(iceCreams[0].PricePerKilogram == 123456789.123456789M);
            Debug.Assert(iceCreams[1].PricePerKilogram == 0.0000001234M);
        }
    }
}

我还查看了您 post 编辑的回购协议。它也可以正常工作。我使用了以下 ExecuteAsync 方法(与您原来的方法没有什么不同):

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    using (TraderDbContext context = new TraderDbContext())
    {
        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();

        // Works without issues.
        TradedCurrencyPair TestPair = new TradedCurrencyPair("one", "two", "Bibox", DateTime.Now, "unknown", 0.1234567890M, 1, 0.1234567890M, 0.1234567890M, 0.1234567890M);
        context.TradedCurrencyPairs.Add(TestPair);
        context.SaveChanges();
    }
    
    using (TraderDbContext context = new TraderDbContext())
    {
        var tradedCurrencyPair = context.TradedCurrencyPairs.Single();
        Debug.Assert(tradedCurrencyPair.LastPrice == 0.1234567890M);
    }
}

EF Core 记录以下 SQL,这是正确的:

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (16ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [TradedCurrencyPairs] (
          [Id] int NOT NULL IDENTITY,
          [TradedCurrency] nvarchar(max) NULL,
          [BaseCurrency] nvarchar(max) NULL,
          [Exchange] nvarchar(max) NULL,
          [DateTime] datetime2 NOT NULL,
          [TransactionType] nvarchar(max) NULL,
          [LastPrice] decimal(20, 10) NOT NULL,
          [ExchangeInternalPairId] int NOT NULL,
          [High24h] decimal(20, 10) NOT NULL,
          [Low24h] decimal(20, 10) NOT NULL,
          [Volume24h] decimal(20, 10) NOT NULL,
          CONSTRAINT [PK_TradedCurrencyPairs] PRIMARY KEY ([Id])
      );

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (79ms) [Parameters=[@p0='two' (Size = 4000), @p1='2020-07-25T04:20:47.8858298+02:00', @p2='Bibox' (Size = 4000), @p3='1', @p4='0.1234567890' (Precision = 10) (Scale = 10), @p5='0.1234567890' (Precision = 10) (Scale = 10), @p6='0.1234567890' (Precision = 10) (Scale = 10), @p7='one' (Size = 4000), @p8='unknown' (Size = 4000), @p9='0.1234567890' (Precision = 10) (Scale = 10)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      INSERT INTO [TradedCurrencyPairs] ([BaseCurrency], [DateTime], [Exchange], [ExchangeInternalPairId], [High24h], [LastPrice], [Low24h], [TradedCurrency], [TransactionType], [Volume24h])
      VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9);
      SELECT [Id]
      FROM [TradedCurrencyPairs]
      WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();

你有什么特殊的数据库端设置吗?触发器、默认值或类似的东西?您可能想要 post 您正在使用的数据库的 CREATE TABLE 脚本(由 SSMS 生成)。

当我从这里发布代码时:

我还是习惯用EF Core 3,所以我启用了EF Core 3的代码。 我不记得我使用了 beta 包并将它们更新到 EF Core 5 预览版。

所以使用这个:

    foreach (var property in modelBuilder.Model.GetEntityTypes()
        .SelectMany(t => t.GetProperties())
        .Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
    {
        
       // EF Core 3
       // property.SetColumnType("decimal(20, 10)");
       // property.SetPrecision(10);
        
        // EF Core 5
        property.SetPrecision(18);
        property.SetScale(6);
    }

而不是这个:

    foreach (var property in modelBuilder.Model.GetEntityTypes()
        .SelectMany(t => t.GetProperties())
        .Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
    {
        
        EF Core 3
        property.SetColumnType("decimal(20, 10)");
        property.SetPrecision(10);
        
        // EF Core 5
        // property.SetPrecision(18);
        // property.SetScale(6);
    }

完美运行。

很抱歉因为我的愚蠢错误浪费了你的时间:/