Entity Framework DateTime und UTC

96

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 eine Möglichkeit, dies in der Zuordnung anzugeben, beispielsweise in dieser für die Spalte last_login:

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");
Fionn
quelle

Antworten:

144

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

Definieren Sie zunächst das 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));
        }
    }
}

Verbinden Sie nun dieses Attribut mit Ihrem EF-Kontext:

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

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

Jetzt können Sie dieses Attribut auf eine DateTimeoder DateTime?mehrere Eigenschaften anwenden:

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

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

Wenn Entity Framework eine Entität aus der Datenbank lädt, wird die von DateTimeKindIhnen angegebene Entität 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 die Art beim Abrufen festlegen, sodass sie als UTC serialisiert oder mit in andere Zeitzonen konvertiert werden kann TimeZoneInfo.

Matt Johnson-Pint
quelle
7
Wenn Sie dies nicht zum Laufen bringen können, fehlt Ihnen wahrscheinlich eine der folgenden Verwendungen: using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Reflection;
Saustrup
7
@Saustrup - Die meisten Beispiele für SO lassen der Kürze halber keine Verwendung zu, es sei denn, sie sind direkt für die Frage relevant. Aber danke.
Matt Johnson-Pint
4
@MattJohnson ohne @ Saustrup verwendet Anweisungen, erhalten Sie einige nicht hilfreiche Kompilierungsfehler wie'System.Array' does not contain a definition for 'Where'
Jacob Eggers
7
Wie @SilverSideDown sagte, funktioniert dies nur mit .NET 4.5. Ich habe einige Erweiterungen erstellt, um die Kompatibilität mit .NET 4.0 unter gist.github.com/munr/3544bd7fab6615290561 zu gewährleisten . Eine andere Sache zu beachten ist, dass dies nicht mit Projektionen funktioniert, sondern nur mit vollständig geladenen Entitäten.
Mun
5
Irgendwelche Vorschläge, wie man dies mit Projektionen in Gang bringen kann?
Jafin
32

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 dekorieren müssen. Daher habe ich Matts Ansatz verallgemeinert, damit der Ereignishandler einen Standardwert für Kind anwenden kann, es sei denn, ein Mitglied ist explizit mit dem Attribut versehen.

Der Konstruktor für die ApplicationDbContext-Klasse enthält diesen 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 aus wie das:

/// <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));
            }
        }
    }
}
Bob.at.Indigo.Health
quelle
1
Dies ist eine sehr nützliche Erweiterung der akzeptierten Antwort!
Lerner
Vielleicht fehlt mir etwas, aber wie ist dies standardmäßig DateTimeKind.Utc im Gegensatz zu DateTimeKind.Unspecified?
Rhonage
1
@ Rhonage Entschuldigung. Die Standardeinstellung wird im ApplicationDbContext-Konstruktor eingerichtet. Ich habe die Antwort aktualisiert, um das einzuschließen.
Bob.at.Indigo.Health
1
@ Bob.at.AIPsychLab Danke Kumpel, jetzt viel klarer. Ich habe versucht herauszufinden, ob es eine gewisse Gewichtsreflexion gibt - aber nein, ganz einfach!
Rhonage
Dies schlägt fehl, wenn ein Modell ein DateTImeAttribut ohne (öffentliche) Setter-Methode hat. Bearbeiten vorgeschlagen. Siehe auch stackoverflow.com/a/3762475/2279059
Florian Winter
13

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 DbCommandInterceptorein von EntityFramework bereitgestelltes Objekt verwenden.

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

Registrieren Sie abschließend die Konfiguration für auf Ihrem DbContext

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

Das ist es. Prost.

Hier ist der Einfachheit halber 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);
        }
    }
}
user2397863
quelle
Bisher scheint dies die beste Antwort zu sein. Ich habe zuerst die Attributvariation ausprobiert, da sie weniger weitreichend zu sein schien, aber meine Komponententests würden mit Verspottung fehlschlagen, da die Verknüpfung der Konstruktorereignisse nichts über Tabellenzuordnungen zu wissen scheint, die im OnModelCreating-Ereignis auftreten. Dieser bekommt meine Stimme!
Der Senator
1
Warum beschattest du Disposeund GetData?
user247702
2
Dieser Code sollte wahrscheinlich @IvanStoev gutschreiben: stackoverflow.com/a/40349051/90287
Rami A.
Leider schlägt dies fehl, wenn Sie räumliche Daten zuordnen
Chris
@ user247702 yea Shadowing Entsorgen ist ein Fehler, überschreiben Entsorgen (bool)
user2397863
9

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 es auch die Zeitzoneninformationen. Wenn Sie diese Daten an eine Webanwendung übergeben (in meinem Fall Angular2), 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.

Moutono
quelle
7
DateTimeOffset speichert die Zeitzone entgegen der allgemeinen Wahrnehmung nicht. Es speichert einen Versatz von UTC, den der Wert darstellt. Der Versatz kann nicht umgekehrt abgebildet werden, um die tatsächliche Zeitzone zu bestimmen, aus der der Versatz erstellt wurde, wodurch der Datentyp nahezu unbrauchbar wird.
Suncat2000
2
Nein, aber es kann verwendet werden, um eine DateTime korrekt zu speichern: medium.com/@ojb500/in-praise-of-datetimeoffset-e0711f991cba
Carl
1
Nur UTC benötigt keinen Standort, da dieser überall gleich ist. Wenn Sie etwas anderes als UTC verwenden, benötigen Sie auch den Ort, andernfalls sind die Zeitinformationen nutzlos, auch bei Verwendung von datetimeoffset.
Horitsu
@ Suncat2000 Es ist bei weitem die sinnvollste Art, einen Zeitpunkt zu speichern. Alle anderen Datums- / Zeittypen geben Ihnen auch nicht die Zeitzone an.
John
1
DATETIMEOFFSET macht das, was das Originalplakat wollte: Speichern Sie Datum und Uhrzeit als UTC, ohne eine (explizite) Konvertierung durchführen zu müssen. @Carl DATETIME, DATETIME2 und DATETIMEOFFSET speichern alle Datums- und Uhrzeitwerte korrekt. Abgesehen davon, dass zusätzlich ein Offset von UTC gespeichert wird, hat DATETIMEOFFSET fast keinen Vorteil. Was Sie in Ihrer Datenbank verwenden, ist Ihr Anruf. Ich wollte nur den Punkt nach Hause fahren, dass es keine Zeitzone speichert, wie viele Leute fälschlicherweise denken.
Suncat2000
5

Es gibt keine Möglichkeit, DataTimeKind im Entity Framework anzugeben. Sie können die Datums- und Uhrzeitwerte vor dem Speichern in db in utc konvertieren und immer davon ausgehen, dass die von db abgerufenen Daten als UTC abgerufen wurden. Die während der Abfrage materalisierten DateTime-Objekte sind jedoch immer "Nicht angegeben". Sie können das DateTimeOffset-Objekt auch anstelle von DateTime bewerten.

Vijay
quelle
5

Ich recherchiere gerade darüber und die meisten dieser Antworten sind nicht gerade großartig. Soweit ich sehen kann, kann EF6 nicht mitgeteilt werden, dass die aus der Datenbank stammenden Daten im UTC-Format vorliegen. In diesem Fall können Sie am einfachsten sicherstellen, dass die DateTime-Eigenschaften Ihres Modells in UTC vorliegen, indem Sie den Setter überprüfen und 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 enthält die geheime Sauce.

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

DateTime.Nowist immer DateTimeKind.Local, daher funktioniert der obige Algorithmus gut für im Code generierte Daten. Meistens.

Sie müssen jedoch vorsichtig sein, da es andere Möglichkeiten DateTimeKind.Unspecifiedgibt, sich in Ihren Code einzuschleichen. Beispielsweise können Sie Ihre Modelle aus JSON-Daten deserialisieren, und Ihr Deserializer-Geschmack ist standardmäßig auf diese Art eingestellt. Es liegt an Ihnen, sich vor lokalisierten Daten zu schützen, die markiert sind DateTimeKind.Unspecified, wenn Sie von jemand anderem als EF zu diesem Setter gelangen.

staa99
quelle
6
Wie ich nach mehreren Jahren des Ringen mit diesem Problem herausgefunden habe, ignoriert EF sowohl Getter- als auch Setter-Methoden, wenn Sie DateTime-Felder anderen Strukturen zuweisen oder auswählen, z. B. einem Datenübertragungsobjekt. In diesen Fällen müssen Sie Kind noch ändern, DateTimeKind.Utcnachdem Ihre Ergebnisse generiert wurden. Beispiel: from o in myContext.Records select new DTO() { BrokenTimestamp = o.BbTimestamp };Setzt alle Arten auf DateTimeKind.Unspecified.
Suncat2000
1
Ich verwende DateTimeOffset seit einiger Zeit mit Entity Framework. Wenn Sie Ihre EF-Entitäten mit dem Datentyp DateTimeOffset angeben, geben alle Ihre EF-Abfragen die Daten mit dem Versatz von UTC zurück, genau wie sie in der Datenbank gespeichert sind. Wenn Sie also Ihren Datentyp in DateTimeOffset anstelle von DateTime geändert haben, benötigen Sie die oben beschriebene Problemumgehung nicht.
Moutono
Das ist gut zu wissen! Danke @Moutono
Laut @ Suncat2000-Kommentar funktioniert dies einfach nicht und sollte entfernt werden
Ben Morris
5

Für EF Core gibt es auf GitHub eine großartige Diskussion zu diesem Thema: https://github.com/dotnet/efcore/issues/4711

Eine Lösung (Dank an Christopher Haws ), die dazu führt, dass alle Daten beim Speichern / Abrufen in der Datenbank als UTC behandelt werden, besteht darin, der OnModelCreatingMethode Ihrer DbContextKlasse Folgendes hinzuzufügen :

var dateTimeConverter = new ValueConverter<DateTime, DateTime>(
    v => v.ToUniversalTime(),
    v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

var nullableDateTimeConverter = new ValueConverter<DateTime?, DateTime?>(
    v => v.HasValue ? v.Value.ToUniversalTime() : v,
    v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v);

foreach (var entityType in builder.Model.GetEntityTypes())
{
    if (entityType.IsQueryType)
    {
        continue;
    }

    foreach (var property in entityType.GetProperties())
    {
        if (property.ClrType == typeof(DateTime))
        {
            property.SetValueConverter(dateTimeConverter);
        }
        else if (property.ClrType == typeof(DateTime?))
        {
            property.SetValueConverter(nullableDateTimeConverter);
        }
    }
}

Auch überprüfen Sie diesen Link , wenn Sie einige Eigenschaften einiger Einrichtungen aus werden als UTC behandelt ausschließen möchten.

Honza Kalfus
quelle
Auf jeden Fall die beste Lösung für mich! Danke
Ben Morris
Funktioniert das mit DateTimeOffset?
Mark Redman
1
@MarkRedman Ich halte es nicht für sinnvoll, denn wenn Sie einen legitimen Anwendungsfall für DateTimeOffset haben, möchten Sie auch die Informationen über die Zeitzone behalten. Siehe docs.microsoft.com/en-us/dotnet/standard/datetime/... oder stackoverflow.com/a/14268167/3979621 für , wenn zwischen Datetime und Datetime zu wählen.
Honza Kalfus
IsQueryTypescheint ersetzt worden zu sein durch IsKeyLess: github.com/dotnet/efcore/commit/…
Mark Tielemans vor
4

Wenn Sie beim Festlegen der Werte darauf achten, UTC-Daten ordnungsgemäß einzugeben, und sich nur darum kümmern, dass DateTimeKind beim Abrufen der Entitäten aus der Datenbank ordnungsgemäß festgelegt wird, lesen Sie meine Antwort hier: https://stackoverflow.com/ a / 9386364/279590

michael.aird
quelle
3

Noch ein Jahr, noch eine Lösung! Dies ist für EF Core.

Ich habe viele DATETIME2(7)Spalten, DateTimedie UTC zugeordnet sind, und speichere sie immer. Ich möchte keinen Versatz speichern, da der Versatz immer Null ist, wenn mein Code korrekt ist.

In der Zwischenzeit habe ich andere Spalten, in denen grundlegende Datums- / Uhrzeitwerte mit unbekanntem Versatz (von Benutzern bereitgestellt) gespeichert werden, sodass sie nur "wie sie sind" gespeichert / angezeigt und nicht mit irgendetwas verglichen werden.

Daher brauche ich eine Lösung, die ich auf bestimmte Spalten anwenden kann.

Definieren Sie eine Erweiterungsmethode UsesUtc:

private static DateTime FromCodeToData(DateTime fromCode, string name)
    => fromCode.Kind == DateTimeKind.Utc ? fromCode : throw new InvalidOperationException($"Column {name} only accepts UTC date-time values");

private static DateTime FromDataToCode(DateTime fromData) 
    => fromData.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(fromData, DateTimeKind.Utc) : fromData.ToUniversalTime();

public static PropertyBuilder<DateTime?> UsesUtc(this PropertyBuilder<DateTime?> property)
{
    var name = property.Metadata.Name;
    return property.HasConversion<DateTime?>(
        fromCode => fromCode != null ? FromCodeToData(fromCode.Value, name) : default,
        fromData => fromData != null ? FromDataToCode(fromData.Value) : default
    );
}

public static PropertyBuilder<DateTime> UsesUtc(this PropertyBuilder<DateTime> property)
{
    var name = property.Metadata.Name;
    return property.HasConversion(fromCode => FromCodeToData(fromCode, name), fromData => FromDataToCode(fromData));
}

Dies kann dann für Eigenschaften im Modell-Setup verwendet werden:

modelBuilder.Entity<CustomerProcessingJob>().Property(x => x.Started).UsesUtc();

Es hat den geringen Vorteil gegenüber Attributen, dass Sie es nur auf Eigenschaften des richtigen Typs anwenden können.

Beachten Sie, dass davon ausgegangen wird, dass die Werte aus der Datenbank in UTC vorliegen, aber nur falsch sind Kind. Daher werden die Werte überwacht, die Sie in der Datenbank speichern möchten, und es wird eine beschreibende Ausnahme ausgelöst, wenn sie nicht UTC sind.

Daniel Earwicker
quelle
1
Dies ist eine großartige Lösung, die höher sein sollte, insbesondere jetzt, da die meisten Neuentwicklungen Core oder .NET 5 verwenden. Imaginäre Bonuspunkte für die UTC-Durchsetzungsrichtlinie - wenn mehr Personen ihre Daten UTC bis zur tatsächlichen Benutzeranzeige beibehalten haben, Wir hätten kaum Datums- / Zeitfehler.
oflahero
1

Für diejenigen, die eine @ MattJohnson-Lösung mit .net Framework 4 wie mir mit Einschränkungen der Reflexionssyntax / -methode erreichen müssen, ist eine geringfügige Änderung 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);
        }  
Sxc
quelle
1

Die Lösung von Matt Johnson-Pint funktioniert, aber wenn alle Ihre DateTimes UTC sein sollen, wäre das Erstellen eines Attributs zu umständlich. So habe ich es vereinfacht:

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

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

    private static void SetDateTimesToUtc(object entity)
    {
        if (entity == null)
        {
            return;
        }

        var properties = entity.GetType().GetProperties();
        foreach (var property in properties)
        {
            if (property.PropertyType == typeof(DateTime))
            {
                property.SetValue(entity, DateTime.SpecifyKind((DateTime)property.GetValue(entity), DateTimeKind.Utc));
            }
            else if (property.PropertyType == typeof(DateTime?))
            {
                var value = (DateTime?)property.GetValue(entity);
                if (value.HasValue)
                {
                    property.SetValue(entity, DateTime.SpecifyKind(value.Value, DateTimeKind.Utc));
                }
            }
        }
    }
}
Mielipuoli
quelle
0

Ein anderer Ansatz wäre, eine Schnittstelle mit den datetime-Eigenschaften zu erstellen und diese in den Teilentitätsklassen zu implementieren. Verwenden Sie dann das SavingChanges-Ereignis, um zu überprüfen, ob das Objekt vom Schnittstellentyp ist, und setzen Sie diese Datums- / Uhrzeitwerte auf die gewünschten Werte. Wenn diese an Datumsangaben erstellt / geändert werden, können Sie sie mit diesem Ereignis füllen.

AD.Net
quelle
0

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