实体框架并发令牌 DateTime 类型的问题
Posted
技术标签:
【中文标题】实体框架并发令牌 DateTime 类型的问题【英文标题】:Issues with Entity Framework Concurrency Token DataTime type 【发布时间】:2012-02-21 17:45:00 【问题描述】:我对 DateTime 的并发令牌有疑问。这是重现问题的简单方法。拥有一个实体:
public class Employee
public int EmployeeID get; set;
public string Name get; set;
[ConcurrencyCheck]
public DateTime LastModified get; set;
一个微不足道的 DbContext:
public class MyContext : DbContext
public DbSet<Employee> Employees get; set;
还有如下代码:
Employee orig;
// Create a row (insert)
using (var context = new MyContext())
orig = new Employee
Name = "Mike",
LastModified = DateTime.Now
;
context.Employees.Add(orig);
context.SaveChanges();
// Update the row, passing the right concurrency token
using (var context = new MyContext())
var clone = new Employee
EmployeeID = orig.EmployeeID,
Name = "Suzanne",
// Pass the concurrency token here
LastModified = orig.LastModified
;
context.Employees.Attach(clone);
// Mark the entity as modified to force an update
context.Entry(clone).State = EntityState.Modified;
// Boom! Currency exception!
context.SaveChanges();
基本上,我创建一个员工,然后对其进行更新。砰!我看SQL(Profiling)上生成的更新语句:
exec sp_executesql N'update [dbo].[Employees]
set [Name] = @0, [LastModified] = @1
where (([EmployeeID] = @2) and ([LastModified] = @3))
',N'@0 nvarchar(max) ,@1 datetime2(7),@2 int,@3 datetime2(7)',@0=N'Suzanne',@1='2012-02-21
12:06:30.0141536',@2=0,@3='2012-02-21 12:06:30.0141536'
该语句对我来说似乎是合理的,但它失败了,即它修改零行就好像 ([LastModified] = @3) 失败了。
我怀疑存在“精度问题”,即位数与存储的位数不匹配。会不会是 .NET 和 SQL 中的 DateTime 表示不匹配?
我尝试在我的 Poco 类中使用 System.Data.SqlTypes.SqlDateTime 而不是 DateTime,希望这将具有正确的精度,但我无法映射它,EF 始终未映射该属性。
解决方案?
【问题讨论】:
(我一开始看错了,贴错了答案,如果你读了请忽略。)因为LastModified
有ConcurrencyCheck
属性,ifDateTime
比 SQL Server 数据类型具有更高的精度,它应该在SaveChanges
之后被截断。如果DateTime
具有less 精度,则应准确存储该值。无论哪种方式,在SaveChanges
之后,LastModified
属性应该与数据库中存储的内容完全匹配。您能否通过检索没有 EF 的日期来检查到底存储了什么?
我有一个类似的问题,我的列类型是datetime
(不是datetime2
),并且出于某种原因,EF 仍然将更新查询中的参数发送为datetime2
。你是怎么解决这个问题的?
更新 - 我通过将 ProviderManifestToken
设置为 2005
解决了我的问题。我使用的是 Code First,因此修复涉及创建我自己的 DbModelBuilder
。见this answer
【参考方案1】:
我发现了问题!其实这里有两个问题:一个是技术问题,一个是语义问题。
技术问题是,无论出于何种原因,EF 将 System.DateTime 作为 datetime(2) SQL 类型发送到 SQL。默认情况下,它确实将 System.DateTime 映射为日期时间。尽管将 SQL 类型强制为 datetime(2),但我实际上并没有成功让 EF 使用 datetime(2) 创建数据库。但如果你事后改变它,它就解决了问题。所以这个问题实际上是一个精度问题。
语义上的问题是,如果你仔细想想,整个是没有意义的。并发令牌是您需要传递给 SQL 以证明您是最后一个读取表的人。但是,因此每次更新行时都需要更新并发令牌。一个排除另一个:如果您尝试将 LastModified 更新为 DateTime.Now,您将遇到并发异常,因为并发令牌不是存储在该行中的那个!
因此,尽管找到了解决技术问题的方法,但整个方案没有意义。
...除非!您找到了一种无需使用 EF 即可更新 LastModified 列的方法。例如,您可以有一个触发器。通常你不会想去那里。
【讨论】:
这就是为什么您不应该自己更新令牌的原因。如果 EF 知道它是一个并发令牌,它应该(并且将)执行适当的查询:UPDATE ... SET token = [new value] WHERE token = [old value]
。这里没有语义问题。以上是关于实体框架并发令牌 DateTime 类型的问题的主要内容,如果未能解决你的问题,请参考以下文章
强制实体框架使用 datetime 而非 datetime2
可以使用实体框架迁移将 DateTime 字段默认为 GETDATE() 吗?