这清楚地表明拥有properties with side effects 是多么棘手。当然,设置属性是一种副作用,但从狭义上讲,它意味着:做的比预期的要多。 IE。违反最小意外原则。
在您的情况下,它会导致两种风格的 LINQ(对象和转换为 SQL)表现不同。 EF 对这种转换感到“惊讶”并且不处理它。关键是通过value conversion 与 EF 共享秘密。
我有一个简单而无聊的类,它有两个属性,其中两个 DateTime 属性都映射到 Sql Server 中的 DateTime(2) 列:
class Class1
{
DateTime _value;
public int ID { get; set; }
public DateTime Value
{
get => _value.ToLocalTime();
set => _value = value.ToUniversalTime();
}
public DateTime ConvValue { get ; set ; }
}
我希望ConvValue 显示当地时间,就像Value 一样,但现在循环中有 EF。所以我添加了这个配置:
class Class1Config : IEntityTypeConfiguration<Class1>
{
public void Configure(EntityTypeBuilder<Class1> builder)
{
builder.Property(c => c.ConvValue)
.HasConversion(v => v.ToUniversalTime(), v => v.ToLocalTime());
// ^^ to database ^^ from database
}
}
然后我将此内容添加到数据库中(注意毫秒和00 小时):
| ID |
Value |
ConvValue |
| 1 |
2020-01-01 00:01:01.0010000 |
2020-01-01 00:01:01.0010000 |
| 2 |
2020-01-01 00:01:01.0020000 |
2020-01-01 00:01:01.0020000 |
| 3 |
2020-01-01 00:01:01.0030000 |
2020-01-01 00:01:01.0030000 |
我通过执行这些查询(在 Linqpad 中)解决了您的问题:
DateTime refd;
using (var db = getContext())
{
refd = db.Classes.First().Value;
}
using (var db = getContext())
{
db.Classes.Count(c => c.Value == refd).Dump(); // 0
db.Classes.AsEnumerable().Count(c => c.Value == refd).Dump(); // 1
}
0 是因为第一个 Count 查询在我的时区运行此 SQL 代码:
exec sp_executesql N'SELECT COUNT(*)
FROM [Classes] AS [c]
WHERE [c].[Value] = @__refd_0',N'@__refd_0 datetime2(7)',
@__refd_0='2020-01-01 01:01:01.0010000'
如您所见,参数是我的本地时间 (+ 1h),因为这是第一个查询生成它的方式(refd 是 2020-01-01 01:01:01.001)。 EF 直接从它获得的模型信息中转换查询。
基于ConvValue 的相同查询给出预期结果:
db.Classes.Count(c => c.ConvValue == refd).Dump(); // 1
db.Classes.AsEnumerable().Count(c => c.ConvValue == refd).Dump(); // 1
现在 EF 知道转换并将参数转换为 UTC,然后再将其转换为 SQL(00 小时):
exec sp_executesql N'SELECT COUNT(*)
FROM [Classes] AS [c]
WHERE [c].[ConvValue] = @__refd_0',N'@__refd_0 datetime2(7)',
@__refd_0='2020-01-01 00:01:01.0010000'