实体框架并发令牌 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 始终未映射该属性。

解决方案?

【问题讨论】:

(我一开始看错了,贴错了答案,如果你读了请忽略。)因为LastModifiedConcurrencyCheck属性,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.now 为空

首先在实体框架代码中使用搜索字符串搜索 DateTime

可以使用实体框架迁移将 DateTime 字段默认为 GETDATE() 吗?

实体框架中的代码首先设置列以在 sql server 中键入 datetime2

使用 forge DA 获取访问令牌的问题