尽管设置了精度,但 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);
}
完美运行。
很抱歉因为我的愚蠢错误浪费了你的时间:/
我的 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);
}
完美运行。
很抱歉因为我的愚蠢错误浪费了你的时间:/