我遇到了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总是将属性取消映射。
解决方案?
答案 0 :(得分:4)
我发现了问题!实际上,这里有两个问题:技术问题和语义问题。
技术问题是,无论出于何种原因,EF都会将System.DateTime作为datetime(2)SQL类型发送给SQL。默认情况下,它会将System.DateTime映射为datetime。尽管强制SQL类型为datetime(2),但实际上我没有成功让EF使用datetime(2)创建数据库。但如果你事后改变它,它就解决了这个问题。所以这个问题确实是一个精确的问题。
语义问题是,如果你考虑它,整个没有意义。您需要将并发令牌传递给SQL,以证明您是最后一个读取该表的人。但是,每次更新行时都需要更新并发令牌。一个排除了另一个:如果你尝试将LastModified更新为DateTime.Now,你会有一个并发异常,因为并发令牌不是存储在行中的那个!
因此,尽管找到了技术问题的解决方案,但整个方案没有意义。
......除非!您可以找到一种在不使用EF的情况下更新LastModified列的方法。例如,你可以有一个触发器。通常你不想去那里。