webentwicklung-frage-antwort-db.com.de

Entity Framework DateTime und UTC

Ist es möglich, dass Entity Framework (ich verwende derzeit den Code First Approach mit CTP5) alle DateTime-Werte als UTC in der Datenbank speichert?

Oder gibt es vielleicht eine Möglichkeit, dies im Mapping anzugeben, zum Beispiel in dieser für die last_login-Spalte:

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

Hier ist ein Ansatz, den Sie in Betracht ziehen könnten:

Definieren Sie zuerst dieses folgende Attribut:

[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<DateTimeKindAttribute>();
            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));
        }
    }
}

Hängen Sie nun dieses Attribut an Ihren EF-Kontext an:

public class MyContext : DbContext
{
    public DbSet<Foo> Foos { get; set; }

    public MyContext()
    {
        ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
            (sender, e) => DateTimeKindAttribute.Apply(e.Entity);
    }
}

Jetzt auf jedem DateTime oder DateTime? Eigenschaften können Sie dieses Attribut anwenden:

public class Foo
{
    public int Id { get; set; }

    [DateTimeKind(DateTimeKind.Utc)]
    public DateTime Bar { get; set; }
}

Mit dieser Option wird beim Laden einer Entität aus der Datenbank durch Entity Framework das von Ihnen angegebene DateTimeKind festgelegt, z. B. UTC.

Beachten Sie, dass dies beim Speichern nichts bewirkt. Sie müssen den Wert noch ordnungsgemäß in UTC konvertieren, bevor Sie versuchen, ihn zu speichern. Sie können jedoch beim Abrufen die Art festlegen, die als UTC serialisiert oder mit TimeZoneInfo in andere Zeitzonen konvertiert werden kann.

133

Ich mag den Ansatz von Matt Johnson sehr, aber in meinem Modell sind ALLE meine DateTime-Mitglieder UTC und ich möchte nicht alle mit einem Attribut verzieren müssen. Deshalb habe ich Matts Ansatz verallgemeinert, damit der Ereignishandler einen Standardwert für "Kind" anwenden kann, es sei denn, ein Mitglied ist ausdrücklich mit dem Attribut versehen.

Der Konstruktor für die ApplicationDbContext-Klasse enthält folgenden Code:

/// <summary> Constructor: Initializes a new ApplicationDbContext instance. </summary>
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 sieht so aus:

/// <summary> Sets the DateTime.Kind value on DateTime and DateTime? members retrieved by Entity Framework. Sets Kind to DateTimeKind.Utc by default. </summary>
[AttributeUsage(AttributeTargets.Property)]
public class DateTimeKindAttribute : Attribute
{
    /// <summary> The DateTime.Kind value to set into the returned value. </summary>
    public readonly DateTimeKind Kind;

    /// <summary> Specifies the DateTime.Kind value to set on the returned DateTime value. </summary>
    /// <param name="kind"> The DateTime.Kind value to set on the returned DateTime value. </param>
    public DateTimeKindAttribute(DateTimeKind kind)
    {
        Kind = kind;
    }

    /// <summary> Event handler to connect to the ObjectContext.ObjectMaterialized event. </summary>
    /// <param name="entity"> The entity (POCO class) being materialized. </param>
    /// <param name="defaultKind"> [Optional] The Kind property to set on all DateTime objects by default. </param>
    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<DateTimeKindAttribute>();
            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));
            }
        }
    }
}
27

Diese Antwort funktioniert mit Entity Framework 6

Die akzeptierte Antwort funktioniert nicht für projizierte oder anonyme Objekte. Leistung könnte auch ein Problem sein.

Um dies zu erreichen, müssen wir ein DbCommandInterceptor verwenden, ein Objekt, das von EntityFramework bereitgestellt wird.

Interceptor erstellen:

public class UtcInterceptor : DbCommandInterceptor
{
    public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        base.ReaderExecuted(command, interceptionContext);

        if (interceptionContext?.Result != null && !(interceptionContext.Result is UtcDbDataReader))
        {
            interceptionContext.Result = new UtcDbDataReader(interceptionContext.Result);
        }
    }
}

interceptionContext.Result ist DbDataReader, den wir durch unseren ersetzen

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

Registrieren Sie den Abfangjäger in Ihrem DbConfiguration

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

Zum Schluss registrieren Sie die Konfiguration für auf Ihrem DbContext

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

Das ist es. Prost.

Der Einfachheit halber ist hier die gesamte Implementierung von 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
{
    /// <inheritdoc />
    [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;
        }

        /// <inheritdoc />
        public override int VisibleFieldCount => source.VisibleFieldCount;

        /// <inheritdoc />
        public override int Depth => source.Depth;

        /// <inheritdoc />
        public override int FieldCount => source.FieldCount;

        /// <inheritdoc />
        public override bool HasRows => source.HasRows;

        /// <inheritdoc />
        public override bool IsClosed => source.IsClosed;

        /// <inheritdoc />
        public override int RecordsAffected => source.RecordsAffected;

        /// <inheritdoc />
        public override object this[string name] => source[name];

        /// <inheritdoc />
        public override object this[int ordinal] => source[ordinal];

        /// <inheritdoc />
        public override bool GetBoolean(int ordinal)
        {
            return source.GetBoolean(ordinal);
        }

        /// <inheritdoc />
        public override byte GetByte(int ordinal)
        {
            return source.GetByte(ordinal);
        }

        /// <inheritdoc />
        public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
        {
            return source.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);
        }

        /// <inheritdoc />
        public override char GetChar(int ordinal)
        {
            return source.GetChar(ordinal);
        }

        /// <inheritdoc />
        public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
        {
            return source.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
        }

        /// <inheritdoc />
        public override string GetDataTypeName(int ordinal)
        {
            return source.GetDataTypeName(ordinal);
        }

        /// <summary>
        /// Returns datetime with Utc kind
        /// </summary>
        public override DateTime GetDateTime(int ordinal)
        {
            return DateTime.SpecifyKind(source.GetDateTime(ordinal), DateTimeKind.Utc);
        }

        /// <inheritdoc />
        public override decimal GetDecimal(int ordinal)
        {
            return source.GetDecimal(ordinal);
        }

        /// <inheritdoc />
        public override double GetDouble(int ordinal)
        {
            return source.GetDouble(ordinal);
        }

        /// <inheritdoc />
        public override IEnumerator GetEnumerator()
        {
            return source.GetEnumerator();
        }

        /// <inheritdoc />
        public override Type GetFieldType(int ordinal)
        {
            return source.GetFieldType(ordinal);
        }

        /// <inheritdoc />
        public override float GetFloat(int ordinal)
        {
            return source.GetFloat(ordinal);
        }

        /// <inheritdoc />
        public override Guid GetGuid(int ordinal)
        {
            return source.GetGuid(ordinal);
        }

        /// <inheritdoc />
        public override short GetInt16(int ordinal)
        {
            return source.GetInt16(ordinal);
        }

        /// <inheritdoc />
        public override int GetInt32(int ordinal)
        {
            return source.GetInt32(ordinal);
        }

        /// <inheritdoc />
        public override long GetInt64(int ordinal)
        {
            return source.GetInt64(ordinal);
        }

        /// <inheritdoc />
        public override string GetName(int ordinal)
        {
            return source.GetName(ordinal);
        }

        /// <inheritdoc />
        public override int GetOrdinal(string name)
        {
            return source.GetOrdinal(name);
        }

        /// <inheritdoc />
        public override string GetString(int ordinal)
        {
            return source.GetString(ordinal);
        }

        /// <inheritdoc />
        public override object GetValue(int ordinal)
        {
            return source.GetValue(ordinal);
        }

        /// <inheritdoc />
        public override int GetValues(object[] values)
        {
            return source.GetValues(values);
        }

        /// <inheritdoc />
        public override bool IsDBNull(int ordinal)
        {
            return source.IsDBNull(ordinal);
        }

        /// <inheritdoc />
        public override bool NextResult()
        {
            return source.NextResult();
        }

        /// <inheritdoc />
        public override bool Read()
        {
            return source.Read();
        }

        /// <inheritdoc />
        public override void Close()
        {
            source.Close();
        }

        /// <inheritdoc />
        public override T GetFieldValue<T>(int ordinal)
        {
            return source.GetFieldValue<T>(ordinal);
        }

        /// <inheritdoc />
        public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken cancellationToken)
        {
            return source.GetFieldValueAsync<T>(ordinal, cancellationToken);
        }

        /// <inheritdoc />
        public override Type GetProviderSpecificFieldType(int ordinal)
        {
            return source.GetProviderSpecificFieldType(ordinal);
        }

        /// <inheritdoc />
        public override object GetProviderSpecificValue(int ordinal)
        {
            return source.GetProviderSpecificValue(ordinal);
        }

        /// <inheritdoc />
        public override int GetProviderSpecificValues(object[] values)
        {
            return source.GetProviderSpecificValues(values);
        }

        /// <inheritdoc />
        public override DataTable GetSchemaTable()
        {
            return source.GetSchemaTable();
        }

        /// <inheritdoc />
        public override Stream GetStream(int ordinal)
        {
            return source.GetStream(ordinal);
        }

        /// <inheritdoc />
        public override TextReader GetTextReader(int ordinal)
        {
            return source.GetTextReader(ordinal);
        }

        /// <inheritdoc />
        public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken)
        {
            return source.IsDBNullAsync(ordinal, cancellationToken);
        }

        /// <inheritdoc />
        public override Task<bool> 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);
        }
    }
}
9
user2397863

Ich glaube, ich habe eine Lösung gefunden, die keine benutzerdefinierte UTC-Überprüfung oder DateTime-Manipulation erfordert.

Grundsätzlich müssen Sie Ihre EF-Entitäten ändern, um den Datentyp DateTimeOffset (NOT DateTime) zu verwenden. Dadurch wird die Zeitzone mit dem Datumswert in der Datenbank gespeichert (in meinem Fall SQL Server 2015).

Wenn EF Core die Daten von der Datenbank anfordert, erhält er auch die Zeitzoneninformationen. Wenn Sie diese Daten an eine Webanwendung (in meinem Fall Angular2) übergeben, wird das Datum automatisch in die lokale Zeitzone des Browsers konvertiert, was ich erwarte.

Und wenn es an meinen Server zurückgegeben wird, wird es automatisch wieder in UTC konvertiert, auch wie erwartet.

7
Moutono

Ich untersuche dies gerade und die meisten dieser Antworten sind nicht gerade großartig. Soweit ich sehen kann, ist es nicht möglich, EF6 mitzuteilen, dass die aus der Datenbank kommenden Daten im UTC-Format vorliegen. In diesem Fall ist es am einfachsten, die DateTime-Eigenschaften Ihres Modells in UTC zu überprüfen und im Setter zu konvertieren.

Hier ist ein C # -ähnlicher Pseudocode, der den Algorithmus beschreibt

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

Die ersten beiden Zweige liegen auf der Hand. Der letzte hält die geheime Sauce.

Wenn EF6 ein Modell aus Daten erstellt, die aus der Datenbank geladen wurden, lauten DateTimes DateTimeKind.Unspecified. Wenn Sie wissen, dass Ihre Daten alle UTC-Werte in der Datenbank sind, funktioniert der letzte Zweig hervorragend für Sie.

DateTime.Now Ist immer DateTimeKind.Local, Daher funktioniert der obige Algorithmus auch für im Code generierte Daten. Meistens.

Sie müssen jedoch vorsichtig sein, da sich DateTimeKind.Unspecified Auf andere Weise in Ihren Code einschleichen kann. Beispielsweise können Sie Ihre Modelle aus JSON-Daten deserialisieren, und die Deserializer-Version ist standardmäßig auf diese Art eingestellt. Es liegt an Ihnen, sich vor lokalisierten Daten zu schützen, die mit DateTimeKind.Unknown Markiert sind, damit niemand außer EF zu diesem Setter gelangt.

6
Will

Wenn Sie beim Festlegen der Werte darauf achten, dass die UTC-Daten ordnungsgemäß übergeben werden, und nur darauf achten, dass DateTimeKind beim Abrufen der Entitäten aus der Datenbank ordnungsgemäß festgelegt wird, lesen Sie meine Antwort hier: https: // stackoverflow.com/a/9386364/27959

4
michael.aird

Es gibt keine Möglichkeit, das DataTimeKind im Entity Framework anzugeben. Sie können entscheiden, die Datums- und Uhrzeitwerte vor dem Speichern in db in utc umzuwandeln und die von db abgerufenen Daten immer als UTC anzunehmen. Die während der Abfrage abgesicherten DateTime-Objekte sind jedoch immer "Nicht angegeben". Sie können auch das DateTimeOffset-Objekt anstelle von DateTime verwenden.

4
Vijay

Für diejenigen, die wie ich eine @MattJohnson-Lösung mit .net Framework 4 mit Reflection-Syntax-/Methodenbeschränkung benötigen, ist eine kleine Modifikation erforderlich, wie unten aufgeführt:

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

In meinem Fall hatte ich nur eine Tabelle mit UTC-Daten. Folgendes habe ich getan:

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;
        }
    }
}
0
Ronnie Overby

Ein anderer Ansatz wäre, eine Schnittstelle mit den datetime-Eigenschaften zu erstellen und diese in den partiellen Entitätsklassen zu implementieren. Verwenden Sie dann das SavingChanges-Ereignis, um zu überprüfen, ob das Objekt vom Schnittstellentyp ist, und setzen Sie diese datetime-Werte auf einen beliebigen Wert. Wenn es sich um Daten handelt, die an einem bestimmten Datum erstellt oder geändert wurden, können Sie dieses Ereignis verwenden, um sie mit Daten zu füllen.

0
AD.Net