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

时间:2014-10-15 21:33:27

标签: ef-code-first decimal entity-framework-6 geospatial rounding-error

我在MVC 5应用程序上使用Code First方法使用Entity Framework 6.x.在这种特殊情况下,我的模型(以及其他内容)包含两个名为Latitude和Longitude的属性:

[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)是同一个问题,但我不知道如何解决这个问题(如果确实如此),因为我已经在相关表格之后执行了多次迁移和数据添加被“迁移”了。

综述

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

2 个答案:

答案 0 :(得分:12)

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

答案 1 :(得分:0)

存储空间数据,我建议为该类型的数据创建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;)时,来自实体框架的插入在Database.Log中的DbContext中看起来像这样:

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

SqlProviderServices.TruncateDecimalsToScale = false;Database.Log的{​​{1}}改为:

DbContext

现在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