实体框架并发令牌 DataTime 类型的问题
我对 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 中的日期时间表示形式是否不匹配?
我尝试在 Poco 类中使用 System.Data.SqlTypes.SqlDateTime 而不是 DateTime,希望这能带来正确的精度,但我无法映射它,EF 始终具有未映射的属性。
解决方案?
I have an issue with concurrency token of DateTime. Here's a simple way to reproduce the problem. Have one entity:
public class Employee
{
public int EmployeeID { get; set; }
public string Name { get; set; }
[ConcurrencyCheck]
public DateTime LastModified { get; set; }
}
A trivial DbContext:
public class MyContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
}
And the following code:
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();
}
Basically, I create an employee, then update it. Bang! I look at the update statement generated on 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'
The statement seems sound to me, but it fails, i.e. it modifies zero row as if ([LastModified] = @3) failed.
I suspect a 'precision problem', i.e. the number of digits mismatched with the one stored. Could it be a mismatch between DateTime representation in .NET and SQL?
I've tried using System.Data.SqlTypes.SqlDateTime instead of DateTime in my Poco class, hoping this would carry the right precision, but I wasn't able to map it, EF always had the property unmapped.
Solutions?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我发现问题了!实际上,这里有两个问题:技术问题和语义问题。
技术问题是 EF 无论出于何种原因,都会将 System.DateTime 作为 datetime(2) SQL 类型发送到 SQL。默认情况下,它确实将 System.DateTime 映射为日期时间。尽管强制 SQL 类型为 datetime(2),但实际上我并没有成功让 EF 使用 datetime(2) 创建数据库。但如果事后改变它,问题就解决了。所以这个问题实际上是一个精度问题。
语义问题是,如果你仔细思考的话,整个内容就没有意义。并发令牌是您需要传递给 SQL 的东西,以证明您是最后一个读取该表的人。但因此每次更新行时都需要更新并发令牌。其中之一排除了另一个:如果您尝试将 LastModified 更新为 DateTime.Now,则会出现并发异常,因为并发令牌不是存储在该行中的令牌!
因此,尽管找到了技术问题的解决方案,但这整个方案没有意义。
... 除非!您找到了一种无需使用 EF 即可更新 LastModified 列的方法。例如,您可以有一个触发器。通常你不会想去那里。
I found the problem! Actually, there are two problems here: a technical one and a semantic one.
The technical problem is that EF, for whatever reason, sends System.DateTime as datetime(2) SQL type to SQL. By default, it does map System.DateTime as datetime though. I actually didn't succeed to have EF create the DB with datetime(2) despite forcing the SQL type to datetime(2). But if you change it after the fact, it solves the problem. So the problem was really a precision problem.
The semantic problem is that the entire doesn't make sense if you think about it. A concurrency token is something you need to pass to SQL to prove you were the last one to read the table. But a concurrency token therefore needs to be updated each time a row is updated. One precludes the other: if you try to update the LastModified to DateTime.Now, you'll have a concurrency exception since the concurrency token isn't the one stored in the row!
So despite finding a solution to the technical problem, this entire scheme doesn't make sense.
... unless! You find a way to update the LastModified column without using EF. You could have a trigger for instance. Typically you wouldn't want to go there though.