实体框架代码首先截断我的小数
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;
) 时,来自实体框架的插入在来自 DbContext
的 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, 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);
【讨论】:
以上是关于实体框架代码首先截断我的小数的主要内容,如果未能解决你的问题,请参考以下文章