Linq到SQLdate时间值是本地(Kind = Unspecified) – 我如何使UTC?
是不是有一种(简单的)方法来告诉Linq To SQL类,一个特定的DateTime属性应该被认为是UTC(也就是说DateTimetypes的Kind属性默认是Utc),还是有一个“干净的”解决方法?
我的应用程序服务器上的时区与SQL 2005 Server不同(不能更改任何),而且没有任何是UTC。 当我将DateTimetypes的属性保存到dB时,我使用UTC值(因此db列中的值为UTC),但是当我读取值(使用Linq To SQL)时,我得到DateTime的.Kind属性价值是“未指定”。
问题是,当我“转换”到UTC是4个小时。 这也意味着当它被序列化的时候,它会在客户端结束,并且有一个4小时的错误偏移量(因为它是使用UTC序列化的)。
生成的LinqToSql代码提供了扩展点,所以您可以在加载对象时设置值。
关键是创build一个扩展生成的类的部分类,然后实现OnLoaded
部分方法。
例如,假设你的类是Person
,所以你在Blah.designer.cs
有一个生成的部分Person
类。
通过创build一个新的类(必须在不同的文件中)来扩展部分类,如下所示:
public partial class Person { partial void OnLoaded() { this._BirthDate = DateTime.SpecifyKind(this._BirthDate, DateTimeKind.Utc); } }
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();
这乍一看似乎是合理的,但根据DateTime.ToUniversalTime的MSDN文档 ,在转换Datetypes为Unspecified的DateTime时:
当前的DateTime对象被假定为本地时间,并且转换的执行类似Kind是Local。
此行为是与.NET 1.x向后兼容性所必需的,它没有DateTime.Kind属性。
我认为这样做的唯一方法就是在部分类中添加一个shim属性来完成翻译…
对于我们的情况,如前所述总是指定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模型创build.tt文件(或者.dbml用于Linq-to-SQL)
2)打开.tt文件并find“WritePrimitiveTypeProperty”方法。
3)replace现有的setter代码。 这是ReportPropertyChanging
和ReportPropertyChanged
方法callback之间的所有内容,具体如下:
<#+ 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)#>); <#+ } #>
@ rob263提供了一个很好的方法。
这只是一个额外的帮助,我希望提供如果您使用entity framework而不是Linq To Sql。
entity framework不支持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); } }
我使用这种方式来即时指定DateTimeKind :
DateTime myDateTime = new DateTime(((DateTime)myUtcValueFromDb).Ticks, DateTimeKind.Utc);
这个代码片段将允许您将从LINQ到SQL返回的DateTimes(Kind = Unspecified)转换为UTC时间,而不会影响时间。
TimeZoneInfo UTCTimeZone = TimeZoneInfo.FindSystemTimeZoneById("UTC"); DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(myLinq2SQLTime, UTCTimeZone);
有可能更干净的方法来做到这一点,但我有这个手,可以快速testing!
我不确定是否有办法让LINQ to SQL类透明地工作 – 您可能希望查看partial类,看看是否可以挂钩读取/写入值。
我希望UTC,TimeZone类可以为你做,如果你想在不同的时区之间转换,比TimeZoneInfo是你的。 使用TimeZoneInfo从我的代码示例:
TimeZoneInfo cet = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); ac.add_datetime = TimeZoneInfo.ConvertTime(DateTime.Now, cet);