Linq to SQL DateTime values are local (Kind=Unspecified) - How do I make it UTC?
是不是有一种(简单)方法告诉Linq To SQL类特定的DateTime属性应该被视为UTC(即默认情况下将DateTime类型的Kind属性设置为Utc),或者是否存在"干净"的解决方法?
我的app-server上的时区与SQL 2005 Server不同(不能更改任何),没有一个是UTC。 当我将类型为DateTime的属性保留为dB时,我使用UTC值(因此db列中的值为UTC),但是当我读回值时(使用Linq To SQL),我得到DateTime的.Kind属性 值为'未指定'。
问题是,当我将它'转换为UTC时,它是4小时关闭。 这也意味着当它被序列化时,它最终在客户端以4小时的错误偏移(因为它使用UTC序列化)。
生成的LinqToSql代码提供了可扩展性点,因此您可以在加载对象时设置值。
关键是要创建一个扩展生成的类的partial类,然后实现
例如,假设您的类是
通过创建新类(必须位于不同的文件中)来扩展分部类,如下所示:
1 2 3 4 5 6 | public partial class Person { partial void OnLoaded() { this._BirthDate = DateTime.SpecifyKind(this._BirthDate, DateTimeKind.Utc); } } |
SQL Server DateTime不包含任何时区或DateTimeKind信息,因此从数据库中检索的DateTime值正确具有Kind = DateTimeKind.Unspecified。
如果您希望将这些时间设为UTC,则应按如下方式"转换"它们:
1 |
或等效的:
1 | DateTime utcDateTime = DateTime.SpecifyKind(databaseDateTime, DateTimeKind.Utc); |
我假设你的问题是你试图将它们转换如下:
1 | DateTime utcDateTime = databaseDateTime.ToUniversalTime(); |
这看起来似乎是合理的,但是根据DateTime.ToUniversalTime的MSDN文档,在转换其类型未指定的DateTime时:
The current DateTime object is assumed
to be a local time, and the conversion
is performed as if Kind were Local.
此行为是与.NET 1.x向后兼容所必需的,它没有DateTime.Kind属性。
对于我们的情况,总是如前所述指定DateTimeKind是不切实际的:
1 | 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代码。这是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <#+ 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)#>); <#+ } #> |
我能想到的唯一方法就是在部分类中添加一个shim属性来进行翻译...
@ rob263提供了一个很好的方法。
如果您使用的是Entity Framework而不是Linq To Sql,这只是我希望提供的额外帮助。
实体框架不支持OnLoaded事件。
相反,您可以执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public partial class Person { protected override void OnPropertyChanged(string property) { if (property =="BirthDate") { this._BirthDate= DateTime.SpecifyKind(this._BirthDate, DateTimeKind.Utc); } base.OnPropertyChanged(property); } } |
我用这种方式动态指定DateTimeKind:
1 |
此代码段将允许您将从LINQ返回到SQL的DateTimes(Kind = Unspecified)转换为UTC时间而不会受到影响。
1 2 | TimeZoneInfo UTCTimeZone = TimeZoneInfo.FindSystemTimeZoneById("UTC"); DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(myLinq2SQLTime, UTCTimeZone); |
可能有更简洁的方法可以做到这一点,但我有这个手头,可以快速测试!
我不确定是否有办法让它透明地使用LINQ to SQL类 - 您可能希望查看partial类并查看是否可以挂钩读取/写入值的位置。
我想要UTC,TimeZone类可以为你做,如果你想在不同的时区之间进行转换,比TimeZoneInfo适合你。使用TimeZoneInfo从我的代码中例证:
1 2 | TimeZoneInfo cet = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); ac.add_datetime = TimeZoneInfo.ConvertTime(DateTime.Now, cet); |