15

When I create a Buyin object the response from the ASP.NET MVC controller, (return Json(response, JsonRequestBehavior.AllowGet);, looks like this:

"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)/"
    },

If I convert that on Epoch Converter I get this time: GMT: Sun, 23 Oct 2016 17:12:18.042 GMT

Looking in the database the stored datetime seems to be correct:

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

When the response is sent out the Kind is set to UTC.

Now I call a controller to get all my data and all of the dates have several hours added to it:

 {
    "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

However when I request this object I can see that the actual object has the correct date set: enter image description here

But the Kind is set to Unspecified so I think this is causing the problem.

For the moment I don't have not set any globalization settings.

So basically my question is: When ASP.NET MVC loads the dates from the database is there a way to tell the server to load the dates with Kind set to UTC as I think that is the problem?

The database is saved and loaded using Entity Framework.

Update after the accepted answer

The accepted answer was great however my date values was already stored in the Database as UTC dates so I modified GetDateTime to this:

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();
}
superjos
  • 12,189
  • 6
  • 89
  • 134
Westerlund.io
  • 2,743
  • 5
  • 30
  • 37

1 Answers1

32

Assuming you are using EF6 and you want to set the Kind property of any DateTime value retrieved from the database to Utc.

Similar questions have been asked, and the answers tend to suggest hooking into the ObjectContext.ObjectMaterialized event, but it didn't fire for queries that use projection.

The solution I'm going to propose works for both entity and projection queries, by performing the conversion at the DbDataReader level (which is used by this type of queries).

In order to do that, we need a custom DbDataReader implementation that intercepts the GetDateTime method. Unfortunately implementing DbDataReader derived class requires a lot of boilerplate code. Luckily I already created a base class form my answer to Dynamic Translate to avoid C# syntax errors which simply delegates each method to the underlying DbDataReader instance, so I'll just take it from there:

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; } }
}

and build the actual class that we need on top of it:

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

Once we have that, we need to plug it into EF infrastructure using EF interception.

We'll start by creating a custom DbCommandInterceptor derived class:

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);
    }
}

register it (for instance from your DbContext derived class static constructor):

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

and we are done.

Now every DateTime value coming from the database will have Kind property set to Utc.

Yannick Motton
  • 34,761
  • 4
  • 39
  • 55
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • 1
    You have a bug in the Interceptor. Because you're not evaluating whether .Result is of type UtcDateTimeConvertingDbDataReader, it starts putting one inside the other. Here's the fix: `if (!(interceptionContext.Result is UtcDateTimeConvertingDbDataReader) &&...` – Jeff Fischer Jun 30 '17 at 01:05
  • Also, you should probably add these to the implementation I think. It helps when you implement the IDataRecord and IDbDataReader interfaces on the class. `public new IDataReader GetData(int i) { return _source.GetData(i); } public new void Dispose() { // Can't dispose, creates a circular reference. this._source.Dispose(); }` – Jeff Fischer Jun 30 '17 at 01:14
  • @TruthOf42 Can't say w/o benchmarking tests, but IMO should be negligible. If you have benchmarking tests, you can run them with and w/o the interceptor and compare the results. – Ivan Stoev Oct 11 '17 at 19:56
  • @JeffFischer What is the purpose of shadowing `GetData` and `Dispose`? – user247702 Sep 04 '18 at 06:42
  • @JeffFischer I'm also curious about the potential bug you mention. How would that scenario occur? – user247702 Sep 04 '18 at 06:45
  • 1
    @Stijn Regarding the bug he mentions, it could happen only if you install the interceptor more than once - which wasn't the idea. There is no need to create `Dispose` or override `Dispose(bool)` because it calls `Close` which is already overridden and delegated to `source`. Also there is no need to shadow `GetData` - you could add `protected override DbDataReader GetDbDataReader(int ordinal) { return source.GetData(ordinal); }` which I've missed (it's not used by EF). – Ivan Stoev Sep 04 '18 at 08:11
  • If you also need persisting Local as UTC and querying with Local, refer to https://stackoverflow.com/a/7108862/618660 – statler Oct 08 '20 at 04:08
  • I wanted to use this but unfortunately anonymous linq queries return the base.getName as the query alias. – John Lord Oct 22 '20 at 18:18
  • This solution does not work if you are using [Spatial Data Type](https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/data-types/spatial). It will result in the following exception: "Spatial readers can only be produced from readers of type SqlDataReader" – Peter Riesz Dec 29 '22 at 10:50