实体框架并发令牌数据时间类型的问题

Vin*_*zon 4 entity-framework code-first ef-code-first

我有 DateTime 的并发令牌问题。这是重现问题的简单方法。拥有一个实体:

public class Employee
{
    public int EmployeeID { get; set; }
    public string Name { get; set; }
    [ConcurrencyCheck]
    public DateTime LastModified { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

一个简单的 DbContext:

public class MyContext : DbContext
{
    public DbSet<Employee> Employees { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

以及以下代码:

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();
}
Run Code Online (Sandbox Code Playgroud)

基本上,我创建一个员工,然后更新它。砰! 我查看在 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'
Run Code Online (Sandbox Code Playgroud)

该语句对我来说似乎是合理的,但它失败了,即将零行修改为好像 ([LastModified] = @3) 失败。

我怀疑存在“精度问题”,即与存储的位数不匹配的位数。.NET 和 SQL 中的 DateTime 表示形式是否不匹配?

我试过在我的 Poco 类中使用 System.Data.SqlTypes.SqlDateTime 而不是 DateTime,希望这能带来正确的精度,但我无法映射它,EF 总是有未映射的属性。

解决方案?

Vin*_*zon 5

我发现了问题!实际上,这里有两个问题:技术问题和语义问题。

技术问题是 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]”。这里不存在语义问题。 (2认同)