Linq to SQL DateTime 值是本地的(Kind=Unspecified)-如何使它成为 UTC?
Posted
技术标签:
【中文标题】Linq to SQL DateTime 值是本地的(Kind=Unspecified)-如何使它成为 UTC?【英文标题】:Linq to SQL DateTime values are local (Kind=Unspecified) - How do I make it UTC? 【发布时间】:2010-10-23 19:48:24 【问题描述】:难道没有一种(简单的)方法可以告诉 Linq To SQL 类某个特定的 DateTime 属性应被视为 UTC(即默认情况下 DateTime 类型的 Kind 属性为 Utc),或者是否有一个 '干净的解决方法?
我的应用服务器上的时区与 SQL 2005 服务器上的时区不同(无法更改),并且没有一个是 UTC。当我将 DateTime 类型的属性持久保存到 dB 时,我使用 UTC 值(因此 db 列中的值是 UTC),但是当我读回这些值(使用 Linq To SQL)时,我得到了 DateTime 的 .Kind 属性值为“未指定”。
问题是,当我将其“转换”为 UTC 时,它会休息 4 小时。这也意味着当它被序列化时,它最终会在客户端出现 4 小时的错误偏移(因为它是使用 UTC 序列化的)。
【问题讨论】:
【参考方案1】:生成的 LinqToSql 代码提供了扩展点,因此您可以在加载对象时设置值。
关键是创建一个扩展生成类的分部类,然后实现OnLoaded
分部方法。
例如,假设您的班级是Person
,那么您在Blah.designer.cs
中生成了部分Person
班级。
通过创建一个新类来扩展部分类(必须在不同的文件中),如下:
public partial class Person
partial void OnLoaded()
this._BirthDate = DateTime.SpecifyKind(this._BirthDate, DateTimeKind.Utc);
【讨论】:
太棒了!这让我能够彻底解决问题! 不幸的是,存储过程 L2S 结果类似乎不提供这种可扩展性 :( 我想我会将 SP 函数的范围重新调整为私有并创建一个指定种类的公共重载。 是的,这是一个很好的解决方法,除了 Stephen 指出的,由于一些不可理解的疯狂原因,您不能对存储过程结果执行此操作。【参考方案2】:SQL Server DateTime 不包含任何时区或 DateTimeKind 信息,因此从数据库中正确检索到的 DateTime 值具有 Kind = DateTimeKind.Unspecified。
如果您想将这些时间设为 UTC,您应该按如下方式“转换”它们:
DateTime utcDateTime = new DateTime(databaseDateTime.Ticks, DateTimeKind.Utc);
或等价物:
DateTime utcDateTime = DateTime.SpecifyKind(databaseDateTime, DateTimeKind.Utc);
我认为您的问题是您尝试将它们转换如下:
DateTime utcDateTime = databaseDateTime.ToUniversalTime();
这乍一看似乎是合理的,但根据the MSDN documentation for DateTime.ToUniversalTime,在转换 Kind 未指定的 DateTime 时:
假定当前的 DateTime 对象 是当地时间,并且转换 就像 Kind 是 Local 一样执行。
此行为对于向后兼容没有 DateTime.Kind 属性的 .NET 1.x 是必需的。
【讨论】:
这是一个很好的解决方法,尽管关于partial void OnLoaded
的答案允许更好的修复
DateTime.SpecifyKind 在后台使用 Ticks - 鉴于自 .NET 2 以来一直存在,我更喜欢那个。【参考方案3】:
我能想到的唯一方法是在进行翻译的部分类中添加一个 shim 属性...
【讨论】:
【参考方案4】:对于我们的例子来说,总是如前所述指定 DateTimeKind 是不切实际的:
DateTime utcDateTime = DateTime.SpecifyKind(databaseDateTime, DateTimeKind.Utc);
我们使用的是Entity Framework,不过应该类似于Linq-to-SQL
如果您想强制将来自数据库的所有 DateTime 对象指定为 UTC,您需要添加一个 T4 转换文件并为所有 DateTime 和可为空的 DateTime 对象添加额外的逻辑,以便将它们初始化为 DateTimeKind。 UTC
我有一篇博客文章逐步解释了这一点:http://www.aaroncoleman.net/post/2011/06/16/Forcing-Entity-Framework-to-mark-DateTime-fields-at-UTC.aspx
简而言之:
1) 为您的 .edmx 模型创建 .tt 文件(或为 Linq-to-SQL 创建 .dbml)
2) 打开.tt文件,找到“WritePrimitiveTypeProperty”方法。
3) 替换现有的 setter 代码。这是 ReportPropertyChanging
和 ReportPropertyChanged
方法回调之间的所有内容,具有以下内容:
<#+ if( ((PrimitiveType)primitiveProperty.TypeUsage.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.DateTime)
#>
if(<#=code.FieldName(primitiveProperty)#> == new DateTime())
<#=code.FieldName(primitiveProperty)#> = StructuralObject.SetValidValue(value<#=OptionalNullableParameterForSetValidValue(primitiveProperty, code)#>);
<#+
if(ef.IsNullable(primitiveProperty))
#>
if(value != null)
<#=code.FieldName(primitiveProperty)#> = DateTime.SpecifyKind(<#=code.FieldName(primitiveProperty)#>.Value, DateTimeKind.Utc);
<#+
else
#>
<#=code.FieldName(primitiveProperty)#> = DateTime.SpecifyKind(<#=code.FieldName(primitiveProperty)#>, DateTimeKind.Utc);
<#+
#>
else
<#=code.FieldName(primitiveProperty)#> = StructuralObject.SetValidValue(value<#=OptionalNullableParameterForSetValidValue(primitiveProperty, code)#>);
<#+
else
#>
<#=code.FieldName(primitiveProperty)#> = StructuralObject.SetValidValue(value<#=OptionalNullableParameterForSetValidValue(primitiveProperty, code)#>);
<#+
#>
【讨论】:
这种方法很棒,但我正在尝试为同样的场景找到解决方案,除非您使用投影在 LINQ 查询上创建新对象。例如,context.Entities.Select(o => new Date = o.DateField )
@Mauricio,如果您使用的是 .edmx 文件,它应该是相似的或相同的。这个想法是您只是使用 T4 转换来覆盖框架在投影时初始化 DateTimes 的方式。
谢谢!实际上,我确实试了一下(当时),就我所记得的,我能够覆盖实体投影,但不能覆盖原始类型。我可能不得不回去检查您是否仍然认为原始类型可以在投影初始化期间被破坏。【参考方案5】:
@rob263 提供了一个很好的方法。
如果您使用的是实体框架而不是 Linq To Sql,这只是我希望提供的额外帮助。
实体框架不支持 OnLoaded 事件。
相反,您可以执行以下操作:
public partial class Person
protected override void OnPropertyChanged(string property)
if (property == "BirthDate")
this._BirthDate= DateTime.SpecifyKind(this._BirthDate, DateTimeKind.Utc);
base.OnPropertyChanged(property);
【讨论】:
【参考方案6】:我使用这种方式即时指定 DateTimeKind:
DateTime myDateTime = new DateTime(((DateTime)myUtcValueFromDb).Ticks, DateTimeKind.Utc);
【讨论】:
DateTime.SpecifyKind 在后台使用 Ticks - 鉴于自 .NET 2 以来一直存在,我更喜欢那个。【参考方案7】:此代码 sn-p 将允许您将从 LINQ 返回到 SQL 的 DateTimes(Kind=Unspecified)转换为 UTC 时间,而不会影响时间。
TimeZoneInfo UTCTimeZone = TimeZoneInfo.FindSystemTimeZoneById("UTC");
DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(myLinq2SQLTime, UTCTimeZone);
可能有更简洁的方法可以做到这一点,但我手头有这个并且可以快速测试它!
我不确定是否有办法让它透明地与 LINQ to SQL 类一起工作 - 您可能想查看部分类,看看是否可以挂钩读取/写入值的位置。
【讨论】:
鉴于 UTC 是一个已知时区,使用 DateTime.SpecifyKind 会更快。如果知道 TimeZone 既不是当前本地 TimeZone 也不是 UTC(例如,EST 中的 DB 服务器和 PST 中的 App 服务器),那将是我唯一希望使用 TimeZoneInfo 的时间。【参考方案8】:如果你想要 UTC,TimeZone 类可以为你做,如果你想在不同的时区之间转换,比 TimeZoneInfo 更适合你。以我的 TimeZoneInfo 代码为例:
TimeZoneInfo cet = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
ac.add_datetime = TimeZoneInfo.ConvertTime(DateTime.Now, cet);
【讨论】:
以上是关于Linq to SQL DateTime 值是本地的(Kind=Unspecified)-如何使它成为 UTC?的主要内容,如果未能解决你的问题,请参考以下文章
LINQ体验(11)——LINQ to SQL语句之Null语义和String/DateTime方法
Linq to SQL 选择 DateTime 字段在任何日期范围列表中的行
Linq to sql 有将datetime转换为string类型方法吗