实体框架代码首先截断我的小数

Posted

技术标签:

【中文标题】实体框架代码首先截断我的小数【英文标题】:Entity Framework Code First truncating my decimals 【发布时间】:2014-12-11 02:48:22 【问题描述】:

我在 MVC 5 应用程序上使用 Code First 方法使用 Entity Framework 6.x。在这种特殊情况下,我的模型(除其他外)包含两个名为纬度和经度的属性:

[Required, Range(-90, +90)]
public decimal Latitude  get; set; 

[Required, Range(-180, +180)]
public decimal Longitude  get; set; 

当我执行迁移时,我得到了类似的东西

CreateTable("ResProperty"), c => new 
        :
    Latitude = c.Decimal(nullable: false, precision: 10, scale: 8),
    Longitude = c.Decimal(nullable: false, precision: 11, scale: 8),
        :
)
... other stuff

所以纬度和经度都有 8 个十进制位。前者有 2 个整数(最多 90),后者有 3 个整数(最多 180)。

执行 Update-Database 命令后,我的表的列显示为:

Latitude decimal(10,8)
Longitude decimal(11,8)

这对我来说似乎很好。现在在我看来,我有一个地图和 javascript 代码,允许用户重新定位标记。这也很好。当标记重新定位时,纬度和经度字段将填充更新的值(Javascript)具有超过 12 个十进制数字。 AFAIK 没关系,因为我的比例是小数点后 8 位。

在按下提交按钮并调用 Create 或 Edit POST 方法后,我检查模型实例并确认模型中传递给控制器​​的实际值是正确的,它们有足够多的十进制数字(那些那个Javascript代码的地方)。所以这个值是正确的。

现在...问题是在执行 db.SaveChanges() 后数据库得到更新 - 我已经确认已经发生了实际的写入/更新 - 但不知何故,EF 在内部忽略了我的实际值并写入截断纬度/经度四舍五入到只有两位十进制数字,所以我的纬度在数据库中显示为 09.500000000 所有其他十进制数字都归零,因为似乎已经发生了四舍五入。

// Prior to SaveChanges()
Latitude = 9.08521879
Longitude = -79.51658792
// After SaveChanges()
Latitude = 9.08000000
Longitude = -79.51000000

如果我给出了正确的比例和精度,并且列也有正确的比例和精度,为什么还要舍入它?为什么 SaveChanges 会改变我的价值观?

我发现这篇文章 (http://weiding331.blogspot.com/2014/01/entity-framework-decimal-value.html) 是同样的问题,但我不知道如何解决这个问题(如果有的话),因为在有问题的表之后我已经执行了几次迁移和数据添加是“迁移”。

总结

模型数据类型正确(十进制) 数据库迁移代码具有正确的精度/比例(lat 10/8 lon 11/8) SQL 数据库列具有正确的精度/比例(lat 10/8,long 11/8) 模型中传递的经纬度值至少有 8 位十进制数字 值的实际写入/更新发生在数据库中,没有错误,但是... 数据库中记录的这两列的值被截断为两位十进制数字,并且 将其他最低有效十进制数字显示为零 (0)

【问题讨论】:

您是否在模型中指定了比例和精度?看看这个问题***.com/questions/3504660/…?另一方面,EF 确实支持空间数据类型... 我认为我不能再使用 OnModelCreating 了,因为该特定迁移在我的迁移列表中下降了几个级别。我在事后添加了它并运行了 Update-Database(没有任何新的迁移),但没有看到它解决了问题。除了数据库上的 SQL 列确实显示了正确的精度,但 EF 在 SaveChanges() 期间在内部截断了它。 【参考方案1】:

EF 具有 SqlProviderServices 的特殊属性(SQL Server 的 SqlClient 提供程序的实现) - TruncateDecimalsToScale。 默认值为 true,因此您可以将其更改为 false 值。例如:

public class DbContextConfiguration : DbConfiguration
    
        public DbContextConfiguration()
        
            var now = SqlProviderServices.Instance;
            SqlProviderServices.TruncateDecimalsToScale = false;
            this.SetProviderServices(SqlProviderServices.ProviderInvariantName, SqlProviderServices.Instance);
        
    

    [DbConfigurationType(typeof(DbContextConfiguration))]
    public class MyContext : DbContext
     ... 

更多信息:https://msdn.microsoft.com/en-us/library/system.data.entity.sqlserver.sqlproviderservices.truncatedecimalstoscale%28v=vs.113%29.aspx

【讨论】:

这看起来正是我正在寻找的,但你能解释一下这里的值是如何设置的吗?有一个变量“now”似乎没有被使用,并且该属性似乎是在静态方法上设置的。这是设置此值的最佳方法吗?我很惊讶这样一个重要的特性没有更多的文档围绕它——小数的截断肯定是一个大问题。谢谢。 @Mark007 这是一个全局配置。当然,这也不是设置这些东西的好地方,但你知道它是 EF ;) 它工作并解决了这个问题,但我认为我需要 ILSpy 并确切了解某个时间点发生了什么。 这被认为是一个错误吗?我看不出它是怎么回事......毕竟缩放是正确的但它仍然不恰当地截断?【参考方案2】:

存储空间数据我会推荐DbGeography 为该类型数据创建的类。

https://docs.microsoft.com/en-us/dotnet/api/system.data.entity.spatial.dbgeography?view=entity-framework-6.2.0


正如@AdrianTarnowski 指出的那样,可以使用SqlProviderServices.TruncateDecimalsToScale = false; 解决截断问题。但是,我想说明为什么会发生这种情况以及为什么 Entity Framework 6.X 会截断十进制值而不是默认舍入。

为了测试我正在使用这样的基本程序:

class Program

    static void Main(string[] args)
    
        var dbContext = new ApplicationDbContext();
        dbContext.TestValues.Add(new TestValue()
        
            Value = 0.0005m
        );
        dbContext.TestValues.Add(new TestValue()
        
            Value = 0.0001m
        );
        dbContext.TestValues.Add(new TestValue()
        
            Value = 0.0007m
        );
        dbContext.SaveChanges();
    


public class TestValue

    public int Id  get; set; 

    public decimal Value  get; set; 


public class DbContextConfiguration : DbConfiguration

    public DbContextConfiguration()
    
        var providerInstance = SqlProviderServices.Instance;
        SqlProviderServices.TruncateDecimalsToScale = true;
        this.SetProviderServices(SqlProviderServices.ProviderInvariantName, SqlProviderServices.Instance);
    


[DbConfigurationType(typeof(DbContextConfiguration))]
public class ApplicationDbContext : DbContext

    public ApplicationDbContext() : base("ApplicationContext")
    
        Database.Log = s => Debug.WriteLine(s);
    

    public DbSet<TestValue> TestValues  get; set; 

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        modelBuilder.Entity<TestValue>().Property(x => x.Value).HasPrecision(18, 3);

        base.OnModelCreating(modelBuilder);
    

默认如下所示:SqlProviderServices.TruncateDecimalsToScale = true;。这是为了防止破坏依赖此行为的现有应用程序。

https://docs.microsoft.com/en-us/dotnet/api/system.data.entity.sqlserver.sqlproviderservices.truncatedecimalstoscale?redirectedfrom=MSDN&view=entity-framework-6.2.0#overloads

当 TruncateDecimalsToScale 正常 (TruncateDecimalsToScale = true;) 时,来自实体框架的插入在来自 DbContextDatabase.Log 中如下所示:

INSERT [dbo].[TestValues]([Value])
VALUES (@0)
SELECT [Id]
FROM [dbo].[TestValues]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()


-- @0: '0,0005' (Type = Decimal, Precision = 18, Scale = 3)

但是查看SQL Server Profiler,对于上面的每个值,发送的实际数据都是0

exec sp_executesql N'INSERT [dbo].[TestValues]([Value])
VALUES (@0)
SELECT [Id]
FROM [dbo].[TestValues]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()',N'@0 decimal(18,3)',@0=0

DbContext 更改为SqlProviderServices.TruncateDecimalsToScale = false; Database.Log 看起来像这样:

INSERT [dbo].[TestValues]([Value])
VALUES (@0)
SELECT [Id]
FROM [dbo].[TestValues]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()


-- @0: '0,0005' (Type = Decimal)

现在SQL Server Profiler 看起来更好并且具有正确的值:

exec sp_executesql N'INSERT [dbo].[TestValues]([Value])
VALUES (@0)
SELECT [Id]
FROM [dbo].[TestValues]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()',N'@0 decimal(4,4)',@0=5

请注意,EntityFrameworkCore 不受此影响。这里舍入是默认的。

POC:

class Program

    static void Main(string[] args)
    
        using (var dbContext = new ApplicationDbContext())
        
            dbContext.TestValues.Add(new TestValue()
            
                Value = 0.0005m
            );
            dbContext.TestValues.Add(new TestValue()
            
                Value = 0.0001m
            );
            dbContext.TestValues.Add(new TestValue()
            
                Value = 0.0007m
            );
            dbContext.SaveChanges();
        
    


public class TestValue

    public int Id  get; set; 

    public decimal Value  get; set; 


public class ApplicationDbContext : DbContext

    public DbSet<TestValue> TestValues  get; set; 

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    
        optionsBuilder
            .UseSqlServer("data source=localhost;initial catalog=;persist security info=True;User Id=;Password=;", providerOptions => providerOptions.CommandTimeout(60));
    

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    
        modelBuilder.Entity<TestValue>().Property(x => x.Value).HasColumnType("decimal(18, 3)");

        base.OnModelCreating(modelBuilder);
    

【讨论】:

以上是关于实体框架代码首先截断我的小数的主要内容,如果未能解决你的问题,请参考以下文章

为啥实体框架在保存到数据库时会截断字符串值?

实体框架4.0在插入之前自动截断/修剪字符串

首先使用实体​​框架代码保存单个实体对象

实体框架代码首先尝试打开错误的数据库

如何首先在实体框架代码中设置身份主键的起始值?

实体框架代码首先进行连接状态检查