使用Entity Framework时遇到的并发标记DateTime类型问题

5

我需要翻译的内容是关于IT技术的,涉及并发令牌和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(分析)上生成的更新语句:

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特性,如果 DateTime具有比SQL Server数据类型更高的精度,则应在SaveChanges之后将其截断。 如果DateTime精度较低,则应该准确地存储该值。 无论哪种方式,在SaveChanges之后,LastModified属性应与数据库中存储的内容完全匹配。您可以通过检索没有EF的日期来检查存储了什么。 - user743382
我有一个类似的问题,我的列类型是 datetime(而不是 datetime2),但 EF 仍然以某种原因将参数发送到更新查询中作为 datetime2。你最终是如何解决这个问题的? - sinelaw
更新 - 我通过将 ProviderManifestToken 设置为 2005 解决了我的问题。我正在使用 Code First,所以解决方法涉及创建自己的 DbModelBuilder。请参见 这个答案 - sinelaw
1个回答

5
我找到了问题所在!实际上,这里有两个问题:一个技术问题和一个语义问题。
技术问题是EF出于某种原因将System.DateTime作为datetime(2) SQL类型发送到SQL。虽然它默认将System.DateTime映射为datetime,但我实际上并没有成功让EF使用datetime(2)创建DB,尽管强制使用SQL类型datetime(2)。但如果你在事后更改它,就可以解决问题。所以问题实际上是一个精度问题。
语义问题是如果你仔细思考的话,整个系统是毫无意义的。并发标记是你需要传递给SQL的东西,以证明你是最后一个读取表格的人。但是并发标记因此需要在每次更新行时进行更新。其中一个排除了另一个:如果你试图将LastModified更新为DateTime.Now,则会出现并发异常,因为并发标记不是存储在行中的标记!
因此,尽管找到了技术问题的解决方案,这整个计划也没有任何意义。
...除非!你找到一种不使用EF更新LastModified列的方法。例如,你可以使用触发器。通常情况下,你不想走这条路。

1
这就是为什么你不应该自己更新令牌。如果 EF 知道它是一个并发令牌,它应该(并且会)执行适当的查询:UPDATE ... SET token = [new value] WHERE token = [old value]。这里没有语义问题。 - sinelaw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接