Entity Framework DateTime ve UTC


98

Entity Framework'ün (şu anda CTP5 ile Code First Approach kullanıyorum) tüm DateTime değerlerini UTC olarak veritabanında depolaması mümkün müdür?

Veya bunu eşlemede belirtmenin bir yolu olabilir mi, örneğin, last_login sütunu için bu örnekte:

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

Yanıtlar:


147

İşte dikkate alabileceğiniz bir yaklaşım:

Öncelikle bu özelliği tanımlayın:

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

Şimdi bu özniteliği EF bağlamınıza bağlayın:

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

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

Şimdi herhangi bir DateTimeveya DateTime?özellikte bu özelliği uygulayabilirsiniz:

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

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

Bunun yerine, Entity Framework veritabanından bir varlığı yüklediğinde DateTimeKind, UTC gibi belirttiğiniz ayarı ayarlar .

Kaydederken bunun hiçbir şey yapmadığını unutmayın. Kaydetmeyi denemeden önce yine de değerin doğru şekilde UTC'ye dönüştürülmesi gerekir. Ancak alırken türü ayarlamanıza izin verir, bu da UTC olarak serileştirilmesine veya ile diğer saat dilimlerine dönüştürülmesine izin verir TimeZoneInfo.


7
Bunu çalıştıramıyorsanız, muhtemelen şu kullanımlardan birini kaçırıyorsunuz: System'i kullanmak; System.Collections.Generic kullanarak; System.ComponentModel.DataAnnotations.Schema kullanarak; System.Linq kullanarak; System.Reflection kullanarak;
Saustrup

8
@Saustrup - SO'daki çoğu örnek, soruyla doğrudan alakalı olmadıkça kısalık kullanımlarını atlayacaktır. Ama teşekkürler.
Matt Johnson-Pint

4
@MattJohnson, @ Saustrup'un ifadelerini kullanmadan, yardımcı olmayan bazı derleme hataları alıyorsunuz'System.Array' does not contain a definition for 'Where'
Jacob Eggers

7
@ SilverSideDown'ın dediği gibi, bu yalnızca .NET 4.5 ile çalışır. Bunu gist.github.com/munr/3544bd7fab6615290561 adresinde .NET 4.0 ile uyumlu hale getirmek için bazı uzantılar oluşturdum . Unutulmaması gereken bir diğer nokta da, bunun projeksiyonlarla değil, sadece tam yüklü varlıklarla çalışmamasıdır.
Mun

5
Bunu tahminlerle devam ettirmek için herhangi bir öneriniz var mı?
Jafin

33

Matt Johnson'ın yaklaşımını gerçekten seviyorum, ancak modelimde TÜM DateTime üyelerim UTC ve hepsini bir öznitelikle dekore etmek istemiyorum. Bu yüzden, Matt'in yaklaşımını, bir üye özellik ile açıkça süslenmedikçe olay işleyicisinin varsayılan bir Kind değeri uygulamasına izin verecek şekilde genelleştirdim.

ApplicationDbContext sınıfının yapıcısı şu kodu içerir:

/// <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 buna benzer:

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

1
Bu, kabul edilen cevabın çok faydalı bir uzantısıdır!
Öğrenci

Belki de bir şeyi kaçırıyorum, ancak bu, DateTimeKind.Unspecified yerine DateTimeKind.Utc için nasıl varsayılan olur?
Rhonage

1
@Rhonage Bunun için üzgünüm. Varsayılan, ApplicationDbContext yapıcısında ayarlanır. Cevabı bunu içerecek şekilde güncelledim.
Bob.at.Indigo.Health

1
@ Bob.at.AIPsychLab Teşekkürler dostum, şimdi çok daha net. Biraz ağırlık olup olmadığını anlamaya çalışıyordum Yansıma - ama hayır, çok basit!
Rhonage

Bir modelin DateTIme(genel) ayarlayıcı yöntemi olmayan bir özniteliği varsa bu başarısız olur . Önerileni düzenleyin. Ayrıca bkz. Stackoverflow.com/a/3762475/2279059
Florian Winter

15

Bu cevap Entity Framework 6 ile çalışır

Kabul edilen yanıt, Öngörülen veya Anonim nesneler için çalışmaz. Performans da bir sorun olabilir.

Bunu başarmak için DbCommandInterceptorEntityFramework tarafından sağlanan a , bir nesneyi kullanmamız gerekir .

Interceptor Oluşturun:

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 bizimki ile değiştirdiğimiz DbDataReader

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

Önleyiciyi cihazınıza kaydedin DbConfiguration

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

Son olarak, yapılandırmayı cihazınızda kaydedin. DbContext

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

Bu kadar. Şerefe.

Basit olması için, işte DbReader uygulamasının tamamı:

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

Şimdiye kadar bu en iyi cevap gibi görünüyor. Öznitelik varyasyonunu önce daha az kapsamlı göründüğü için denedim, ancak yapıcı olay bağının OnModelCreating olayında meydana gelen tablo eşleştirmeleri hakkında bilgi sahibi olmadığı için birim testlerim alay ederek başarısız oluyordu. Bu benim oyumu alacak!
Senatör

1
Neden gölgeliyorsun Disposeve GetData?
user247702

2
Bu kod muhtemelen @IvanStoev'e atıfta bulunmalıdır: stackoverflow.com/a/40349051/90287
Rami A.

Uzamsal verileri
Chris

@ user247702 evet gölgelendirme Dispose is error, override Dispose (bool)
user2397863

12

İçin EF Çekirdek , GitHub üzerinde bu konuda büyük bir tartışma vardır: https://github.com/dotnet/efcore/issues/4711

Tüm tarihleri ​​UTC olarak depolarken / veritabanından alırken tüm tarihlerin ele alınmasına neden olacak bir çözüm ( Christopher Haws'a kredi ) OnModelCreating, DbContextsınıfınızın yöntemine aşağıdakileri eklemektir :

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

Ayrıca, bazı varlıkların bazı özelliklerinin UTC olarak değerlendirilmemesini istiyorsanız bu bağlantıyı kontrol edin .


Kesinlikle benim için en iyi çözüm! Teşekkürler
Ben Morris

1
@MarkRedman Bunun mantıklı olduğunu düşünmüyorum, çünkü DateTimeOffset için meşru bir kullanım durumunuz varsa, saat dilimi hakkındaki bilgileri de saklamak istersiniz. DateTime ve DateTimeOffset arasında ne zaman seçim yapılacağını öğrenmek için docs.microsoft.com/en-us/dotnet/standard/datetime/… veya stackoverflow.com/a/14268167/3979621 adresine bakın .
Honza Kalfus

2
IsQueryTypeile değiştirilmiş gibi görünüyor IsKeyLess: github.com/dotnet/efcore/commit/…
Mark Tielemans

Neden IsQueryType(veya IsKeyLessşimdi) kontrol gerekli?
Piotr Perak

9

Herhangi bir özel UTC kontrolü veya DateTime değişikliği gerektirmeyen bir çözüm bulduğuma inanıyorum.

Temel olarak, EF varlıklarınızı DateTimeOffset (NOT DateTime) veri türünü kullanacak şekilde değiştirmeniz gerekir. Bu, saat dilimini veritabanındaki tarih değeriyle birlikte depolayacaktır (benim durumumda SQL Server 2015).

EF Core, DB'den veri talep ettiğinde, saat dilimi bilgilerini de alır. Bu verileri bir web uygulamasına (benim durumumda Angular2) aktardığınızda, tarih otomatik olarak beklediğim gibi tarayıcının yerel saat dilimine dönüştürülür.

Ve sunucuma geri gönderildiğinde, beklendiği gibi otomatik olarak tekrar UTC'ye dönüştürülür.


8
DateTimeOffset, genel algının aksine, saat dilimini saklamaz. Değerin temsil ettiği UTC'den bir fark depolar. Uzaklık, uzaklığın oluşturulduğu gerçek saat dilimini belirlemek için tersine eşlenemez, böylece veri türü neredeyse işe yaramaz hale gelir.
Suncat2000

2
Hayır, ancak bir DateTime'ı doğru şekilde saklamak için kullanılabilir: medium.com/@ojb500/in-praise-of-datetimeoffset-e0711f991cba
Carl

1
Sadece UTC'nin bir konuma ihtiyacı yoktur çünkü her yerde aynıdır. UTC dışında bir şey kullanırsanız, konuma da ihtiyacınız olur, aksi takdirde zaman bilgisi, datetimeoffset kullanıldığında da yararsızdır.
Horitsu

1
DATETIMEOFFSET, orijinal gönderenin istediğini yapacaktır: herhangi bir (açık) dönüştürme yapmak zorunda kalmadan tarih-saati UTC olarak saklayın. @Carl DATETIME, DATETIME2 ve DATETIMEOFFSET, tarih-saat değerini doğru şekilde depolar. Ek olarak UTC'den bir uzaklık depolamak dışında, DATETIMEOFFSET'in neredeyse hiçbir avantajı yoktur. Veritabanınızda kullandığınız şey sizin aramanızdır. Sadece birçok insanın yanlışlıkla düşündüğü gibi bir saat dilimini kaydetmediği noktaya eve gitmek istedim .
Suncat2000

1
@ Suncat2000 Avantajı, bu tarihi olduğu gibi API'nizden istemci tarayıcınıza gönderebilmenizdir. İstemci tarayıcısı bu tarihi açtığında, ofsetin UCT'den ne olduğunu bilir ve bu nedenle istemcinin görüntülediği sistemdeki varsayılan tarihe dönüştürebilir. Böylece, sunucunuzun saat diliminden tarayıcının saat dilimine dönüşüm, geliştiricinin herhangi bir kod yazmasına gerek kalmadan gerçekleşir.
Moutono

6

Şu anda bunu araştırıyorum ve bu cevapların çoğu tam olarak harika değil. Gördüğüm kadarıyla EF6'ya veritabanından çıkan tarihlerin UTC formatında olduğunu söylemenin bir yolu yok. Durum böyleyse, modelinizin DateTime özelliklerinin UTC'de olduğundan emin olmanın en basit yolu, ayarlayıcıda doğrulamak ve dönüştürmek olacaktır.

İşte algoritmayı tanımlayan bazı c # benzeri sözde kodlar

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

İlk iki dal açıktır. Sonuncusu gizli sosu tutar.

EF6, veritabanından yüklenen verilerden bir model oluşturduğunda, DateTimes DateTimeKind.Unspecified. Tarihlerinizin tamamının db'de UTC olduğunu biliyorsanız, son şube sizin için harika çalışacaktır.

DateTime.Nowher zaman DateTimeKind.Localolduğundan, yukarıdaki algoritma kodda oluşturulan tarihler için iyi çalışır. Çoğu zaman.

Ancak, DateTimeKind.Unspecifiedkodunuza gizlice girmenin başka yolları da olduğu için dikkatli olmalısınız . Örneğin, modellerinizi JSON verilerinden kaldırabilirsiniz ve seri kaldırma çeşidiniz varsayılan olarak bu türdedir. DateTimeKind.UnspecifiedEF haricinde herhangi birinden o ayarlayıcıya gelmeden yerelleştirilmiş tarihlere karşı korunmak size kalmıştır .


6
Bu sorunla birkaç yıl uğraştıktan sonra öğrendiğim gibi, DateTime alanlarını başka yapılara, örneğin bir veri aktarım nesnesine atıyor veya seçiyorsanız, EF hem alıcı hem de ayarlayıcı yöntemlerini göz ardı eder. Bu durumlarda, DateTimeKind.Utcsonuçlarınız oluşturulduktan sonra Kind'i yine de değiştirmeniz gerekir. Örnek: from o in myContext.Records select new DTO() { BrokenTimestamp = o.BbTimestamp };tüm Tür'ü olarak ayarlar DateTimeKind.Unspecified.
Suncat2000

1
Bir süredir Entity Framework ile DateTimeOffset kullanıyorum ve EF varlıklarınızı bir DateTimeOffset veri türü ile belirtirseniz, tüm EF sorgularınız tarihleri ​​UTC'den uzaklıkla, tıpkı DB'ye kaydedildiği gibi döndürür. Dolayısıyla, veri türünüzü DateTime yerine DateTimeOffset olarak değiştirdiyseniz, yukarıdaki geçici çözüme ihtiyacınız olmaz.
Moutono

Bunu bilmek güzel! Teşekkürler @Moutono

@ Suncat2000 yorumuna göre, bu işe yaramıyor ve kaldırılması gerekiyor
Ben Morris

5

Entity Framework DataTimeKind öğesini belirtmenin bir yolu yoktur. Db'ye kaydetmeden önce tarih saat değerlerini utc'ye dönüştürmeye karar verebilir ve her zaman verilerin db'den UTC olarak alındığını varsayabilirsiniz. Ancak sorgu sırasında materyalleştirilen DateTime nesneleri her zaman "Belirtilmemiş" olacaktır. DateTime yerine DateTimeOffset nesnesini kullanarak da değerlendirebilirsiniz.


5

Başka bir yıl, başka bir çözüm! Bu EF Core içindir.

Ben bir sürü var DATETIME2(7)eşlemek sütunlar DateTimeve her zaman UTC saklayın. Bir ofset saklamak istemiyorum çünkü kodum doğruysa ofset her zaman sıfır olacaktır.

Bu arada, bilinmeyen ofsetin (kullanıcılar tarafından sağlanan) temel tarih-saat değerlerini depolayan başka sütunlara sahibim, böylece bunlar "olduğu gibi" saklanıyor / görüntüleniyor ve hiçbir şeyle karşılaştırılmıyor.

Bu nedenle, belirli sütunlara uygulayabileceğim bir çözüme ihtiyacım var.

Bir uzantı yöntemi tanımlayın 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));
}

Bu, daha sonra model kurulumundaki özelliklerde kullanılabilir:

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

Özniteliklere göre küçük bir avantajı vardır, bunu yalnızca doğru türdeki özelliklere uygulayabilirsiniz.

DB'den gelen değerlerin UTC'de olduğunu varsaydığını, ancak sadece yanlış olduğunu unutmayın Kind. Bu nedenle, DB'de saklamaya çalıştığınız değerleri denetleyerek UTC değilse açıklayıcı bir istisna atar.


1
Bu, özellikle yeni geliştirmelerin çoğu Core veya .NET 5 kullanacağından daha yüksek olması gereken harika bir çözümdür. UTC uygulama politikası için bonus hayali puanlar - daha fazla kişi tarihlerini UTC gerçek kullanıcı görüntüsüne kadar tutarsa, tarih / saat hatamız neredeyse hiç yok.
oflahero


1

Benim gibi .net framework 4 ile @MattJohnson çözümüne ulaşmak isteyenler için, yansıma sözdizimi / yöntem sınırlaması ile, aşağıda listelendiği gibi biraz değişiklik gerektirir:

     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

Matt Johnson-Pint'in çözümü işe yarıyor, ancak tüm Tarih Saatlerinin UTC olması gerekiyorsa, bir öznitelik oluşturmak çok dolambaçlı olacaktır. İşte bunu nasıl basitleştirdim:

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

0

Diğer bir yaklaşım, datetime özellikleriyle bir arabirim oluşturmak ve bunları kısmi varlık sınıflarında uygulamaktır. Ve sonra, nesnenin arabirim türünde olup olmadığını kontrol etmek için SavingChanges olayını kullanın, bu tarih saat değerlerini istediğiniz şekilde ayarlayın. Aslında, bunlar tür tarihlerde oluşturulmuş / değiştirilmişse, bunları doldurmak için bu olayı kullanabilirsiniz.


fena fikir değil ama sınıflar anonim seçimlerde kullanılmayacak.
John Rab

0

Benim durumumda, UTC tarih saatleri olan tek bir tablom vardı. İşte yaptığım şey:

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

Buradaki çözümler kullanışlıdır, ancak birçoğunun buna tüm veri zamanlarının yerel saat diliminde mevcut olmasını isteme sorunuyla gelmesini bekliyorum, ancak kalıcı sürümün UTC olarak kaydedilmesi için çevrilmesini istiyorlar.

Bunu uygulamak için 3 zorluk vardır:

  1. Verilerin UTC olarak okunması ve Yerel'e dönüştürülmesi
  2. Sorgu parametrelerini ayarlama örn. SEÇ * SATIŞ <@ 1 olan ÜRÜN'den
  3. UTC olarak LocalTime olan verileri saklama

1. Verilerin UTC olarak okunması ve Yerel'e dönüştürülmesi

Bu durumda, veritabanından yüklendikten sonra Ivan Stoev DateTime.Kind'in UTC'ye değil belirtilmemiş olarak ayarlanan çalışmasına dayanan yukarıdaki çözüm ihtiyacınız olanı yapacaktır.

2. Sorgu parametrelerini ayarlama

Ivan'ın durdurucu çözümüne benzer şekilde, ReaderExecuting durdurucuyu kullanabilirsiniz. Bonus, uygulamanın ReaderExecuted'den çok daha kolay olmasıdır.

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        foreach (DbParameter dbParameter in command.Parameters)
        {
            if (dbParameter.Value is DateTime dtLocal)
            {
                if (dtLocal.Kind != DateTimeKind.Utc)
                {
                    dbParameter.Value = dtLocal.ToUniversalTime();
                }
            }
        }
        base.ReaderExecuting(command, interceptionContext);
    }

3. LocalTime olan verileri UTC olarak saklama

Burada yardımcı olacak gibi görünen sorgu engelleyicileri olsa da, bunlar birden çok kez çağrılır ve beklenmedik sonuçlar yaratır. Bulduğum en iyi çözüm SaveChanges'ı geçersiz kılmaktı

    public override int SaveChanges()
    {
        UpdateCommonProperties();
        UpdateDatesToUtc();
        bool saveFailed;
        do
        {
            saveFailed = false;
            try
            {
                var result = base.SaveChanges();
                return result;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                saveFailed = ConcurrencyExceptionHandler(ex);
            }

        } while (saveFailed);
        return 0;
    }

    private void UpdateDatesToUtc()
    {
        if (!ChangeTracker.HasChanges()) return;

        var modifiedEntries = ChangeTracker.Entries().Where(x => (x.State == EntityState.Added || x.State == EntityState.Modified));

        foreach (var entry in modifiedEntries)
        {
            entry.ModifyTypes<DateTime>(ConvertToUtc);
            entry.ModifyTypes<DateTime?>(ConvertToUtc);
        }
    }

    private static DateTime ConvertToUtc(DateTime dt)
    {
        if (dt.Kind == DateTimeKind.Utc) return dt;
        return dt.ToUniversalTime();
    }

    private static DateTime? ConvertToUtc(DateTime? dt)
    {
        if (dt?.Kind == DateTimeKind.Utc) return dt;
        return dt?.ToUniversalTime();
    }

Ve uzantı (Talon https://stackoverflow.com/a/39974362/618660 tarafından verilen yanıta göre)

public static class TypeReflectionExtension
{
    static Dictionary<Type, PropertyInfo[]> PropertyInfoCache = new Dictionary<Type, PropertyInfo[]>();

    static void TypeReflectionHelper()
    {
        PropertyInfoCache = new Dictionary<Type, PropertyInfo[]>();
    }

    public static PropertyInfo[] GetTypeProperties(this Type type)
    {
        if (!PropertyInfoCache.ContainsKey(type))
        {
            PropertyInfoCache[type] = type.GetProperties();
        }
        return PropertyInfoCache[type];
    }

    public static void ModifyTypes<T>(this DbEntityEntry dbEntityEntry, Func<T, T> method)
    {
        foreach (var propertyInfo in dbEntityEntry.Entity.GetType().GetTypeProperties().Where(p => p.PropertyType == typeof(T) && p.CanWrite))
        {
            propertyInfo.SetValue(dbEntityEntry.Entity, method(dbEntityEntry.CurrentValues.GetValue<T>(propertyInfo.Name)));
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.