【问题标题】:DateTime.Kind set to unspecified, not UTC, upon loading from database从数据库加载时,DateTime.Kind 设置为未指定,而不是 UTC
【发布时间】:2017-03-05 11:47:42
【问题描述】:

当我创建一个Buyin 对象时,来自 ASP.NET MVC 控制器的响应(return Json(response, JsonRequestBehavior.AllowGet);,如下所示:

"Buyin": {
        "Id": 95,
        "PlayerSessionId": 88,
        "PlayerId": 45,
        "PlayerName": "Alan",
        "Amount": 888,
        "BuyinType": "Credits",
        "Description": null,
        "Authorized": true,
        "SignPath": "~/Signs/Buyins\\95.png",
        "Payment": null,
        "CreationDate": "/Date(1477242738042)/"
    },

如果我在 Epoch Converter 上转换它,我会得到这个时间:GMT: Sun, 23 Oct 2016 17:12:18.042 GMT

在数据库中查看存储的日期时间似乎是正确的:

95  NULL    1   1   2016-10-23 17:12:18.043

发送响应时,Kind 设置为 UTC

现在我调用一个控制器来获取我的所有数据,并且所有日期都添加了几个小时:

 {
    "Id": 95,
    "PlayerSessionId": 88,
    "PlayerId": 45,
    "PlayerName": "Alan",
    "Amount": 888,
    "BuyinType": "Credits",
    "Description": null,
    "Authorized": true,
    "SignPath": "~/Signs/Buyins\\95.png",
    "Payment": null,
    "CreationDate": "/Date(1477267938043)/"
}

1477267938043 = GMT: Mon, 24 Oct 2016 00:12:18.043 GMT

但是,当我请求此对象时,我可以看到实际对象具有正确的日期集:

但是 Kind 设置为 Unspecified 所以我认为这是导致问题的原因。

目前我没有设置任何全球化设置。

所以基本上我的问题是:当 ASP.NET MVC 从数据库加载日期时,有没有办法告诉服务器加载日期,并将 Kind 设置为 UTC,因为我认为这是问题所在?

使用实体框架保存和加载数据库。

接受答案后更新

接受的答案很好,但是我的日期值已经作为 UTC 日期存储在数据库中,所以我将 GetDateTime 修改为:

public override DateTime GetDateTime(int ordinal)
{
    var date = base.GetDateTime(ordinal);
    var utcDate = DateTime.SpecifyKind(date, DateTimeKind.Utc);
    return utcDate;
    //return base.GetDateTime(ordinal).ToUniversalTime();
}

【问题讨论】:

  • 如何从数据库中加载数据?实体框架?
  • 是的先生,我会更新我的问题。
  • 您可以使用DbContextObjectMaterialized 事件为所有日期指定日期时间类型。另一种方法 - 添加只读属性(仅限 getter),它将指定日期时间类型。
  • 您说问题来自这种属性为空,但究竟是什么让您这么认为?显示器 ?代码中的异常?
  • @Antoine 它来自KindUnspecified,正如您在屏幕截图中看到的那样。这就是我认为的问题。

标签: asp.net entity-framework sql-server-2012


【解决方案1】:

假设您使用的是 EF6,并且您希望将从数据库中检索到的任何 DateTime 值的 Kind 属性设置为 Utc

有人提出了类似的问题,答案往往建议挂钩到 ObjectContext.ObjectMaterialized 事件,但对于使用投影的查询没有触发。

我要提出的解决方案适用于实体查询和投影查询,方法是在DbDataReader 级别(由此类查询使用)执行转换。

为了做到这一点,我们需要一个自定义的DbDataReader 实现来拦截GetDateTime 方法。不幸的是,实现DbDataReader 派生类需要大量样板代码。幸运的是,我已经从我对Dynamic Translate to avoid C# syntax errors 的回答中创建了一个基类,它只是将每个方法委托给底层的DbDataReader 实例,所以我将从那里获取它:

abstract class DelegatingDbDataReader : DbDataReader
{
    readonly DbDataReader source;
    public DelegatingDbDataReader(DbDataReader source)
    {
        this.source = source;
    }
    public override object this[string name] { get { return source[name]; } }
    public override object this[int ordinal] { get { return source[ordinal]; } }
    public override int Depth { get { return source.Depth; } }
    public override int FieldCount { get { return source.FieldCount; } }
    public override bool HasRows { get { return source.HasRows; } }
    public override bool IsClosed { get { return source.IsClosed; } }
    public override int RecordsAffected { get { return source.RecordsAffected; } }
    public override bool GetBoolean(int ordinal) { return source.GetBoolean(ordinal); }
    public override byte GetByte(int ordinal) { return source.GetByte(ordinal); }
    public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) { return source.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length); }
    public override char GetChar(int ordinal) { return source.GetChar(ordinal); }
    public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) { return source.GetChars(ordinal, dataOffset, buffer, bufferOffset, length); }
    public override string GetDataTypeName(int ordinal) { return source.GetDataTypeName(ordinal); }
    public override DateTime GetDateTime(int ordinal) { return source.GetDateTime(ordinal); }
    public override decimal GetDecimal(int ordinal) { return source.GetDecimal(ordinal); }
    public override double GetDouble(int ordinal) { return source.GetDouble(ordinal); }
    public override IEnumerator GetEnumerator() { return source.GetEnumerator(); }
    public override Type GetFieldType(int ordinal) { return source.GetFieldType(ordinal); }
    public override float GetFloat(int ordinal) { return source.GetFloat(ordinal); }
    public override Guid GetGuid(int ordinal) { return source.GetGuid(ordinal); }
    public override short GetInt16(int ordinal) { return source.GetInt16(ordinal); }
    public override int GetInt32(int ordinal) { return source.GetInt32(ordinal); }
    public override long GetInt64(int ordinal) { return source.GetInt64(ordinal); }
    public override string GetName(int ordinal) { return source.GetName(ordinal); }
    public override int GetOrdinal(string name) { return source.GetOrdinal(name); }
    public override string GetString(int ordinal) { return source.GetString(ordinal); }
    public override object GetValue(int ordinal) { return source.GetValue(ordinal); }
    public override int GetValues(object[] values) { return source.GetValues(values); }
    public override bool IsDBNull(int ordinal) { return source.IsDBNull(ordinal); }
    public override bool NextResult() { return source.NextResult(); }
    public override bool Read() { return source.Read(); }
    public override void Close() { source.Close(); }
    public override T GetFieldValue<T>(int ordinal) { return source.GetFieldValue<T>(ordinal); }
    public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken cancellationToken) { return source.GetFieldValueAsync<T>(ordinal, cancellationToken); }
    public override Type GetProviderSpecificFieldType(int ordinal) { return source.GetProviderSpecificFieldType(ordinal); }
    public override object GetProviderSpecificValue(int ordinal) { return source.GetProviderSpecificValue(ordinal); }
    public override int GetProviderSpecificValues(object[] values) { return source.GetProviderSpecificValues(values); }
    public override DataTable GetSchemaTable() { return source.GetSchemaTable(); }
    public override Stream GetStream(int ordinal) { return source.GetStream(ordinal); }
    public override TextReader GetTextReader(int ordinal) { return source.GetTextReader(ordinal); }
    public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken) { return source.IsDBNullAsync(ordinal, cancellationToken); }
    public override Task<bool> ReadAsync(CancellationToken cancellationToken) { return source.ReadAsync(cancellationToken); }
    public override int VisibleFieldCount { get { return source.VisibleFieldCount; } }
}

并在其上构建我们需要的实际类:

class UtcDateTimeConvertingDbDataReader : DelegatingDbDataReader
{
    public UtcDateTimeConvertingDbDataReader(DbDataReader source) : base(source) { }
    public override DateTime GetDateTime(int ordinal)
    {
        return DateTime.SpecifyKind(base.GetDateTime(ordinal), DateTimeKind.Utc);
    }
}

一旦我们有了它,我们需要使用 EF interception 将其插入 EF 基础架构。

我们将从创建一个自定义的DbCommandInterceptor 派生类开始:

class UtcDateTimeConvertingDbCommandInterceptor : DbCommandInterceptor
{
    public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        base.ReaderExecuted(command, interceptionContext);
        if (!(interceptionContext.Result is UtcDateTimeConvertingDbDataReader)
            && interceptionContext.Result != null
            && interceptionContext.Exception == null)
            interceptionContext.Result = new UtcDateTimeConvertingDbDataReader(interceptionContext.Result);
    }
}

注册它(例如从您的DbContext 派生类静态构造函数):

public class YourDbContext : DbContext
{
    static YourDbContext()
    {
        DbInterception.Add(new UtcDateTimeConvertingDbCommandInterceptor());
    }
    // ...
}

我们完成了。

现在来自数据库的每个DateTime 值都将Kind 属性设置为Utc

【讨论】:

  • 你在拦截器中有一个错误。因为您没有评估 .Result 是否属于 UtcDateTimeConvertingDbDataReader 类型,所以它开始将一个放入另一个。这是修复:if (!(interceptionContext.Result is UtcDateTimeConvertingDbDataReader) &amp;&amp;...
  • 另外,您可能应该将这些添加到我认为的实现中。当您在类上实现 IDataRecord 和 IDbDataReader 接口时,它会有所帮助。 public new IDataReader GetData(int i) { return _source.GetData(i); } public new void Dispose() { // Can't dispose, creates a circular reference. this._source.Dispose(); }
  • @TruthOf42 不能说没有基准测试,但 IMO 应该可以忽略不计。如果您有基准测试,您可以使用和不使用拦截器运行它们并比较结果。
  • @JeffFischer 跟踪GetDataDispose 的目的是什么?
  • @Stijn 关于他提到的错误,只有当您多次安装拦截器时才会发生这种情况 - 这不是想法。无需创建Dispose 或覆盖Dispose(bool),因为它调用了已经被覆盖并委托给sourceClose。也无需隐藏GetData - 您可以添加我错过的protected override DbDataReader GetDbDataReader(int ordinal) { return source.GetData(ordinal); }(EF 不使用它)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-25
  • 2013-09-18
  • 1970-01-01
  • 2023-04-02
相关资源
最近更新 更多