Entity Framework DateTime e UTC

E ‘ansible avere Entity Framework (sto usando il Code First Approach con CTP5 al momento) memorizzare tutti i valori DateTime come UTC nel database?

O forse c’è un modo per specificarlo nella mapping, per esempio in questo per la colonna last_login:

modelBuilder.Entity().Property(x => x.Id).HasColumnName("id"); modelBuilder.Entity().Property(x => x.IsAdmin).HasColumnName("admin"); modelBuilder.Entity().Property(x => x.IsEnabled).HasColumnName("enabled"); modelBuilder.Entity().Property(x => x.PasswordHash).HasColumnName("password_hash"); modelBuilder.Entity().Property(x => x.LastLogin).HasColumnName("last_login"); 

Ecco un approccio che potresti prendere in considerazione:

Innanzitutto, definisci questo attributo seguente:

 [AttributeUsage(AttributeTargets.Property)] public class DateTimeKindAttribute : Attribute { private readonly DateTimeKind _kind; public DateTimeKindAttribute(DateTimeKind kind) { _kind = kind; } public DateTimeKind Kind { get { return _kind; } } public static void Apply(object entity) { if (entity == null) return; var properties = entity.GetType().GetProperties() .Where(x => x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(DateTime?)); foreach (var property in properties) { var attr = property.GetCustomAttribute(); if (attr == null) continue; var dt = property.PropertyType == typeof(DateTime?) ? (DateTime?) property.GetValue(entity) : (DateTime) property.GetValue(entity); if (dt == null) continue; property.SetValue(entity, DateTime.SpecifyKind(dt.Value, attr.Kind)); } } } 

Ora collega questo attributo al tuo contesto EF:

 public class MyContext : DbContext { public DbSet Foos { get; set; } public MyContext() { ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += (sender, e) => DateTimeKindAttribute.Apply(e.Entity); } } 

Ora su qualsiasi DateTime o DateTime? proprietà, puoi applicare questo attributo:

 public class Foo { public int Id { get; set; } [DateTimeKind(DateTimeKind.Utc)] public DateTime Bar { get; set; } } 

Con questo in atto, ogni volta che Entity Framework carica un’ quadro dal database, imposterà il DateTimeKind specificato, come l’UTC.

Si noti che questo non fa nulla durante il salvataggio. Dovrai comunque avere il valore correttamente convertito in UTC prima di provare a salvarlo. Ma ti consente di impostare il tipo al momento del recupero, che consente di essere serializzato come UTC o convertito in altri fusi orari con TimeZoneInfo .

Mi piace molto l’approccio di Matt Johnson, ma nel mio modello TUTTI i miei membri DateTime sono UTC e non voglio dover decorarli tutti con un attributo. Quindi ho generalizzato l’approccio di Matt per consentire al gestore di eventi di applicare un valore di tipo predefinito, a meno che un membro non sia esplicitamente decorato con l’attributo.

Il costruttore per la class ApplicationDbContext include questo codice:

 ///  Constructor: Initializes a new ApplicationDbContext instance.  public ApplicationDbContext() : base(MyApp.ConnectionString, throwIfV1Schema: false) { // Set the Kind property on DateTime variables retrieved from the database ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += (sender, e) => DateTimeKindAttribute.Apply(e.Entity, DateTimeKind.Utc); } 

DateTimeKindAttribute presenta così:

 ///  Sets the DateTime.Kind value on DateTime and DateTime? members retrieved by Entity Framework. Sets Kind to DateTimeKind.Utc by default.  [AttributeUsage(AttributeTargets.Property)] public class DateTimeKindAttribute : Attribute { ///  The DateTime.Kind value to set into the returned value.  public readonly DateTimeKind Kind; ///  Specifies the DateTime.Kind value to set on the returned DateTime value.  ///  The DateTime.Kind value to set on the returned DateTime value.  public DateTimeKindAttribute(DateTimeKind kind) { Kind = kind; } ///  Event handler to connect to the ObjectContext.ObjectMaterialized event.  ///  The entity (POCO class) being materialized.  ///  [Optional] The Kind property to set on all DateTime objects by default.  public static void Apply(object entity, DateTimeKind? defaultKind = null) { if (entity == null) return; // Get the PropertyInfos for all of the DateTime and DateTime? properties on the entity var properties = entity.GetType().GetProperties() .Where(x => x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(DateTime?)); // For each DateTime or DateTime? property on the entity... foreach (var propInfo in properties) { // Initialization var kind = defaultKind; // Get the kind value from the [DateTimekind] attribute if it's present var kindAttr = propInfo.GetCustomAttribute(); if (kindAttr != null) kind = kindAttr.Kind; // Set the Kind property if (kind != null) { var dt = (propInfo.PropertyType == typeof(DateTime?)) ? (DateTime?)propInfo.GetValue(entity) : (DateTime)propInfo.GetValue(entity); if (dt != null) propInfo.SetValue(entity, DateTime.SpecifyKind(dt.Value, kind.Value)); } } } } 

Credo di aver trovato una soluzione che non richiede alcun controllo UTC personalizzato o manipolazione DateTime.

Fondamentalmente è necessario cambiare le entity framework EF per utilizzare DateTimeOffset (NOT DateTime) datatype. Ciò memorizzerà il fuso orario con il valore di data nel database (SQL Server 2015 nel mio caso).

Quando EF Core richiede i dati dal DB, riceverà anche le informazioni sul fuso orario. Quando si passano questi dati a un’applicazione Web (Angular2 nel mio caso), la data viene automaticamente convertita nel fuso orario locale del browser, che è quello che mi aspetto.

E quando viene restituito al mio server, viene automaticamente convertito in UTC, anche come previsto.

Sto facendo ricerche su questo adesso, e la maggior parte di queste risposte non è esattamente eccezionale. Da quello che vedo, non c’è modo di dire a EF6 che le date che escono dal database sono in formato UTC. In questo caso, il modo più semplice per assicurarsi che le proprietà DateTime del modello siano in UTC è verificare e convertire nel setter.

Ecco uno pseudocodice simile a c # che descrive l’algoritmo

 public DateTime MyUtcDateTime { get { return _myUtcDateTime; } set { if(value.Kind == DateTimeKind.Utc) _myUtcDateTime = value; else if (value.Kind == DateTimeKind.Local) _myUtcDateTime = value.ToUniversalTime(); else _myUtcDateTime = DateTime.SpecifyKind(value, DateTimeKind.Utc); } } 

I primi due rami sono ovvi. L’ultimo contiene la salsa segreta.

Quando EF6 crea un modello dai dati caricati dal database, DateTimes sono DateTimeKind.Unspecified . Se sai che le date sono tutte UTC nel db, allora l’ultimo ramo funzionerà alla perfezione per te.

DateTime.Now è sempre DateTimeKind.Local , quindi l’algoritmo di cui sopra funziona bene per le date generate nel codice. La maggior parte delle volte.

Devi essere cauto, tuttavia, poiché ci sono altri modi in cui DateTimeKind.Unspecified può introdursi nel tuo codice. Ad esempio, potresti deserializzare i tuoi modelli da dati JSON e il tuo deserializzatore ha un valore predefinito di questo tipo. Spetta a te difenderti dalle date localizzate contrassegnate con DateTimeKind.Unknown da quando sei arrivato da chiunque tranne EF.

Non esiste alcun modo per specificare DataTimeKind in Entity Framework. Potresti decidere di convertire i valori di data e ora in utc prima di archiviare in db e assumere sempre i dati recuperati da db come UTC. Ma gli oggetti DateTime materalizzati durante la query saranno sempre “non specificati”. Puoi anche valutare utilizzando l’object DateTimeOffset invece di DateTime.

Se fai attenzione a passare correttamente le date UTC quando imposti i valori e tutto quello che ti interessa è assicurarti che DateTimeKind sia impostato correttamente quando le quadro vengono recuperate dal database, vedi la mia risposta qui: https://stackoverflow.com/ un / 9386364/279590

Questa risposta funziona con Entity Framework 6

La risposta accettata non funziona per oggetti proiettati o anonimi. Anche le prestazioni potrebbero essere un problema.

Per raggiungere questo objective, è necessario utilizzare DbCommandInterceptor , un object fornito da EntityFramework.

Crea Interceptor:

 public class UtcInterceptor : DbCommandInterceptor { public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { base.ReaderExecuted(command, interceptionContext); if (interceptionContext?.Result != null && !(interceptionContext.Result is UtcDbDataReader)) { interceptionContext.Result = new UtcDbDataReader(interceptionContext.Result); } } } 

interceptionContext.Result è DbDataReader, che sostituiamo con il nostro

 public class UtcDbDataReader : DbDataReader { private readonly DbDataReader source; public UtcDbDataReader(DbDataReader source) { this.source = source; } public override DateTime GetDateTime(int ordinal) { return DateTime.SpecifyKind(source.GetDateTime(ordinal), DateTimeKind.Utc); } // you need to fill all overrides. Just call the same method on source in all cases public new void Dispose() { source.Dispose(); } public new IDataReader GetData(int ordinal) { return source.GetData(ordinal); } } 

Registrare l’intercettore in DbConfiguration

 internal class MyDbConfiguration : DbConfiguration { protected internal MyDbConfiguration () { AddInterceptor(new UtcInterceptor()); } } 

Infine, registra la configurazione sul tuo DbContext

 [DbConfigurationType(typeof(MyDbConfiguration ))] internal class MyDbContext : DbContext { // ... } 

Questo è tutto. Saluti.

Per semplicità, ecco l’intera implementazione di DbReader:

 using System; using System.Collections; using System.Data; using System.Data.Common; using System.IO; using System.Threading; using System.Threading.Tasks; namespace MyNameSpace { ///  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1010:CollectionsShouldImplementGenericInterface")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] public class UtcDbDataReader : DbDataReader { private readonly DbDataReader source; public UtcDbDataReader(DbDataReader source) { this.source = source; } ///  public override int VisibleFieldCount => source.VisibleFieldCount; ///  public override int Depth => source.Depth; ///  public override int FieldCount => source.FieldCount; ///  public override bool HasRows => source.HasRows; ///  public override bool IsClosed => source.IsClosed; ///  public override int RecordsAffected => source.RecordsAffected; ///  public override object this[string name] => source[name]; ///  public override object this[int ordinal] => source[ordinal]; ///  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); } ///  /// Returns datetime with Utc kind ///  public override DateTime GetDateTime(int ordinal) { return DateTime.SpecifyKind(source.GetDateTime(ordinal), DateTimeKind.Utc); } ///  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(int ordinal) { return source.GetFieldValue(ordinal); } ///  public override Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) { return source.GetFieldValueAsync(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 IsDBNullAsync(int ordinal, CancellationToken cancellationToken) { return source.IsDBNullAsync(ordinal, cancellationToken); } ///  public override Task ReadAsync(CancellationToken cancellationToken) { return source.ReadAsync(cancellationToken); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")] public new void Dispose() { source.Dispose(); } public new IDataReader GetData(int ordinal) { return source.GetData(ordinal); } } } 

Per coloro che hanno bisogno di raggiungere la soluzione @MattJohnson con .net framework 4 come me, con la syntax di reflection / limitazione del metodo, richiede una piccola modifica come elencato di seguito:

  foreach (var property in properties) { DateTimeKindAttribute attr = (DateTimeKindAttribute) Attribute.GetCustomAttribute(property, typeof(DateTimeKindAttribute)); if (attr == null) continue; var dt = property.PropertyType == typeof(DateTime?) ? (DateTime?)property.GetValue(entity,null) : (DateTime)property.GetValue(entity, null); if (dt == null) continue; //If the value is not null set the appropriate DateTimeKind; property.SetValue(entity, DateTime.SpecifyKind(dt.Value, attr.Kind) ,null); } 

Un altro approccio sarebbe quello di creare un’interfaccia con le proprietà datetime, implementarle sulle classi quadro parziali. Quindi utilizzare l’evento SavingChanges per verificare se l’object è del tipo di interfaccia, impostare quei valori datetime su qualsiasi cosa si desideri. Infatti, se questi sono stati creati / modificati su date, puoi usarli per popolarli.

Nel mio caso, avevo solo una tabella con datet UTC. Ecco cosa ho fatto:

 public partial class MyEntity { protected override void OnPropertyChanged(string property) { base.OnPropertyChanged(property); // ensure that values coming from database are set as UTC // watch out for property name changes! switch (property) { case "TransferDeadlineUTC": if (TransferDeadlineUTC.Kind == DateTimeKind.Unspecified) TransferDeadlineUTC = DateTime.SpecifyKind(TransferDeadlineUTC, DateTimeKind.Utc); break; case "ProcessingDeadlineUTC": if (ProcessingDeadlineUTC.Kind == DateTimeKind.Unspecified) ProcessingDeadlineUTC = DateTime.SpecifyKind(ProcessingDeadlineUTC, DateTimeKind.Utc); default: break; } } }