是否没有一种(简单)方法告诉 Linq To SQL 类特定的 DateTime 属性应被视为 UTC(即,默认情况下 DateTime 类型的 Kind 属性为 Utc),或者是否有“干净”解决方法?
我的应用程序服务器上的时区与 SQL 2005 Server 不同(无法更改任何时区),并且都不是 UTC。当我将 DateTime 类型的属性保留到 dB 时,我使用 UTC 值(因此 db 列中的值是 UTC),但是当我读回这些值(使用 Linq To SQL)时,我得到 DateTime 的 .Kind 属性值为“未指定”。
问题是,当我将其“转换”为 UTC 时,就晚了 4 个小时。这也意味着当它被序列化时,它最终会在客户端出现 4 小时错误的偏移量(因为它是使用 UTC 序列化的)。
生成的 LinqToSql 代码提供了扩展点,因此您可以在加载对象时设置值。
关键是创建一个扩展生成类的分部类,然后实现
OnLoaded
分部方法。
例如,假设您的班级是
Person
,因此您在 Person
中有一个生成的部分 Blah.designer.cs
班级。
通过创建新类来扩展分部类(必须位于不同的文件中),如下所示:
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 文档,在转换 Kind 未指定的 DateTime 时:
假设当前的 DateTime 对象 为当地时间,并进行转换 执行时就好像 Kind 是本地的一样。
此行为对于向后兼容 .NET 1.x 是必要的,因为 .NET 1.x 没有 DateTime.Kind 属性。
我能想到的唯一方法是在执行翻译的部分类中添加垫片属性...
对于我们的情况,如前所述始终指定 DateTimeKind 是不切实际的:
DateTime utcDateTime = DateTime.SpecifyKind(databaseDateTime, DateTimeKind.Utc);
我们正在使用实体框架,但这应该类似于 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
简而言之:
为您的 .edmx 模型创建 .tt 文件(或为 Linq-to-SQL 创建 .dbml)
打开.tt文件并找到“WritePrimitiveTypeProperty”方法。
替换现有的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)#>);
<#+
}
#>
@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);
}
}
我使用这种方式来动态指定 DateTimeKind:
DateTime myDateTime = new DateTime(((DateTime)myUtcValueFromDb).Ticks, DateTimeKind.Utc);
此代码片段将允许您将从 LINQ to SQL 返回的日期时间(Kind=未指定)转换为 UTC 时间,而不影响时间。
TimeZoneInfo UTCTimeZone = TimeZoneInfo.FindSystemTimeZoneById("UTC");
DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(myLinq2SQLTime, UTCTimeZone);
可能有更干净的方法可以做到这一点,但我手头上有这个并且可以快速测试它!
我不确定是否有办法让它透明地与 LINQ to SQL 类一起工作 - 您可能想查看部分类,看看是否可以挂钩读取/写入值的位置。
我想要UTC,TimeZone类可以为你做到这一点,如果你想在不同时区之间进行转换,那么TimeZoneInfo适合你。以我的 TimeZoneInfo 代码为例:
TimeZoneInfo cet = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
ac.add_datetime = TimeZoneInfo.ConvertTime(DateTime.Now, cet);