Entity Framework DateTime dan UTC

96

Apakah mungkin untuk memiliki Entity Framework (saya menggunakan Pendekatan Pertama Kode dengan CTP5 saat ini) menyimpan semua nilai DateTime sebagai UTC dalam database?

Atau mungkin ada cara untuk menentukannya di pemetaan, misalnya di kolom ini untuk kolom 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
sumber

Jawaban:

145

Inilah satu pendekatan yang dapat Anda pertimbangkan:

Pertama, tentukan atribut berikut ini:

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

Sekarang kaitkan atribut itu ke konteks EF Anda:

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

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

Sekarang di salah satu DateTimeatau DateTime?properti, Anda dapat menerapkan atribut ini:

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

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

Dengan ini, setiap kali Entity Framework memuat entitas dari database, itu akan mengatur DateTimeKindyang Anda tentukan, seperti UTC.

Perhatikan bahwa ini tidak melakukan apa pun saat menyimpan. Anda masih harus memiliki nilai yang dikonversi dengan benar ke UTC sebelum Anda mencoba menyimpannya. Tapi itu memungkinkan Anda untuk mengatur jenis saat mengambil, yang memungkinkan untuk diserialkan sebagai UTC, atau diubah ke zona waktu lain dengan TimeZoneInfo.

Matt Johnson-Pint
sumber
7
Jika Anda tidak bisa mendapatkan ini bekerja, Anda mungkin kehilangan salah satu dari penggunaan ini: using System; menggunakan System.Collections.Generic; menggunakan System.ComponentModel.DataAnnotations.Schema; menggunakan System.Linq; menggunakan System.Reflection;
Saustrup
7
@Saustrup - Anda akan menemukan sebagian besar contoh di SO akan menghilangkan penggunaan untuk singkatnya, kecuali mereka secara langsung relevan dengan pertanyaan. Tapi terima kasih.
Matt Johnson-Pint
4
@MattJohnson tanpa @ Saustrup menggunakan pernyataan, Anda mendapatkan beberapa kesalahan kompilasi yang tidak membantu seperti'System.Array' does not contain a definition for 'Where'
Jacob Eggers
7
Seperti yang dikatakan @SilverSideDown, ini hanya berfungsi dengan .NET 4.5. Saya telah membuat beberapa ekstensi agar kompatibel dengan .NET 4.0 di gist.github.com/munr/3544bd7fab6615290561 . Hal lain yang perlu diperhatikan adalah bahwa ini tidak akan berfungsi dengan proyeksi, hanya entitas yang dimuat penuh.
Mun
5
Adakah saran untuk menjalankan ini dengan proyeksi?
Jafin
32

Saya sangat menyukai pendekatan Matt Johnson, tetapi dalam model saya SEMUA anggota DateTime saya adalah UTC dan saya tidak ingin menghiasi semuanya dengan atribut. Jadi saya menggeneralisasi pendekatan Matt untuk memungkinkan event handler menerapkan nilai Kind default kecuali jika anggota secara eksplisit didekorasi dengan atribut tersebut.

Konstruktor untuk kelas ApplicationDbContext menyertakan kode ini:

/// <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 terlihat seperti ini:

/// <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
sumber
1
Ini adalah ekstensi yang sangat berguna untuk jawaban yang diterima!
Pelajar
Mungkin saya melewatkan sesuatu, tetapi bagaimana ini default ke DateTimeKind.Utc sebagai lawan DateTimeKind.Unspecified?
Rhonage
1
@Rhonage Maaf tentang itu. Standarnya disiapkan di konstruktor ApplicationDbContext. Saya memperbarui jawaban untuk memasukkan itu.
Bob.at.Indigo.Health
1
@ Bob.at.AIPsychLab Terima kasih sobat, jauh lebih jelas sekarang. Mencoba untuk mencari tahu apakah ada beberapa Refleksi berat yang terjadi - tapi tidak, sangat sederhana!
Rhonage
Ini gagal jika model memiliki DateTImeatribut tanpa metode penyetel (publik). Edit yang disarankan. Lihat juga stackoverflow.com/a/3762475/2279059
Musim Dingin Florian
13

Jawaban ini berfungsi dengan Entity Framework 6

Jawaban yang diterima tidak berfungsi untuk objek Proyeksi atau Anonim. Performa juga bisa menjadi masalah.

Untuk mencapai ini, kita perlu menggunakan DbCommandInterceptor, sebuah objek yang disediakan oleh EntityFramework.

Buat Interceptor:

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 adalah DbDataReader, yang kita gantikan dengan milik kita

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

Daftarkan interseptor di file DbConfiguration

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

Terakhir, daftarkan konfigurasi untuk di DbContext

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

Itu dia. Bersulang.

Untuk kesederhanaan, berikut adalah keseluruhan implementasi 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);
        }
    }
}
pengguna2397863
sumber
Sejauh ini sepertinya jawaban terbaik. Saya mencoba variasi atribut terlebih dahulu karena sepertinya jangkauannya kurang jauh tetapi pengujian unit saya akan gagal dengan mengejek karena ikatan acara konstruktor tampaknya tidak tahu tentang pemetaan tabel yang terjadi di acara OnModelCreating. Yang ini mendapat suara saya!
Senator
1
Mengapa Anda membayangi Disposedan GetData?
pengguna247702
2
Kode ini mungkin harus memberi kredit pada @IvanStoev: stackoverflow.com/a/40349051/90287
Rami
Sayangnya ini gagal jika Anda memetakan data Spasial
Chris
@ user247702 yea shadowing Buang adalah kesalahan, timpa Buang (bool)
pengguna2397863
9

Saya yakin saya telah menemukan solusi yang tidak memerlukan pemeriksaan UTC atau manipulasi DateTime khusus.

Pada dasarnya Anda perlu mengubah entitas EF Anda untuk menggunakan datatype DateTimeOffset (BUKAN DateTime). Ini akan menyimpan zona waktu dengan nilai tanggal di database (SQL Server 2015 dalam kasus saya).

Saat EF Core meminta data dari DB, EF Core akan menerima info zona waktu juga. Ketika Anda meneruskan data ini ke aplikasi web (Angular2 dalam kasus saya) tanggal secara otomatis diubah ke zona waktu lokal browser yang saya harapkan.

Dan ketika dikirimkan kembali ke server saya, itu diubah menjadi UTC lagi secara otomatis, juga seperti yang diharapkan.

Moutono
sumber
7
DateTimeOffset tidak menyimpan zona waktu, bertentangan dengan persepsi umum. Ini menyimpan offset dari UTC yang diwakili oleh nilai. Offset tidak dapat dipetakan secara terbalik untuk menentukan zona waktu aktual dari offset tersebut, sehingga membuat tipe data hampir tidak berguna.
Suncat2000
2
Tidak, tetapi dapat digunakan untuk menyimpan DateTime dengan benar: medium.com/@ojb500/in-praise-of-datetimeoffset-e0711f991cba
Carl
1
Hanya UTC yang tidak membutuhkan lokasi, karena lokasinya sama di semua tempat. Jika Anda menggunakan sesuatu selain UTC, Anda juga memerlukan lokasinya, jika tidak informasi waktu tidak berguna, juga menggunakan datetimeoffset.
Horitsu
@ Suncat2000 Sejauh ini cara yang paling masuk akal untuk menyimpan suatu titik waktu. Semua jenis tanggal / waktu lainnya juga tidak memberi Anda zona waktu.
Yohanes
1
DATETIMEOFFSET akan melakukan apa yang diinginkan pengirim asli: menyimpan tanggal-waktu sebagai UTC tanpa harus melakukan konversi (eksplisit) apa pun. @Carl DATETIME, DATETIME2, dan DATETIMEOFFSET semua menyimpan nilai tanggal-waktu dengan benar. Selain menyimpan offset dari UTC, DATETIMEOFFSET hampir tidak memiliki keuntungan sama sekali. Apa yang Anda gunakan dalam database Anda adalah panggilan Anda. Saya hanya ingin pulang ke rumah pada titik bahwa itu tidak menyimpan zona waktu karena banyak orang keliru berpikir.
Suncat2000
6

Untuk EF Core , ada diskusi bagus tentang topik ini di GitHub: https://github.com/dotnet/efcore/issues/4711

Solusi (kredit untuk Christopher Haws ) yang akan menghasilkan perlakuan terhadap semua tanggal saat menyimpannya ke / mengambilnya dari database sebagai UTC adalah dengan menambahkan yang berikut ini ke OnModelCreatingmetode DbContextkelas Anda :

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

Juga, periksa tautan ini jika Anda ingin mengecualikan beberapa properti dari beberapa entitas agar tidak diperlakukan sebagai UTC.

Honza Kalfus
sumber
Pasti solusi terbaik untuk saya! Terima kasih
Ben Morris
1
@MarkRedman Saya rasa tidak masuk akal, karena jika Anda memiliki kasus penggunaan yang sah untuk DateTimeOffset, Anda ingin menyimpan informasi tentang zona waktu juga. Lihat docs.microsoft.com/en-us/dotnet/standard/datetime/… atau stackoverflow.com/a/14268167/3979621 untuk mengetahui kapan harus memilih antara DateTime dan DateTimeOffset.
Honza Kalfus
1
IsQueryTypetampaknya telah digantikan oleh IsKeyLess: github.com/dotnet/efcore/commit/…
Mark Tielemans
5

Tidak ada cara untuk menentukan DataTimeKind di Entity Framework. Anda dapat memutuskan untuk mengubah nilai waktu tanggal ke utc sebelum menyimpan ke db dan selalu menganggap data yang diambil dari db sebagai UTC. Tapi objek DateTime yang dibuat selama kueri akan selalu "Tidak ditentukan". Anda juga bisa mengevaluasi menggunakan objek DateTimeOffset daripada DateTime.

Vijay
sumber
5

Saya sedang meneliti ini sekarang, dan sebagian besar jawaban ini tidak terlalu bagus. Dari apa yang saya lihat, tidak ada cara untuk memberi tahu EF6 bahwa tanggal keluar dari database dalam format UTC. Jika demikian, cara termudah untuk memastikan properti DateTime model Anda dalam UTC adalah dengan memverifikasi dan mengonversi penyetel.

Berikut beberapa pseudocode mirip c # yang menjelaskan algoritme

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

Dua cabang pertama sudah jelas. Yang terakhir memegang saus rahasia.

Ketika EF6 membuat model dari data yang dimuat dari database, DateTimes adalah DateTimeKind.Unspecified. Jika Anda tahu tanggal Anda semua UTC di db, maka cabang terakhir akan bekerja dengan baik untuk Anda.

DateTime.Nowselalu DateTimeKind.Local, jadi algoritme di atas berfungsi dengan baik untuk tanggal yang dihasilkan dalam kode. Sebagian besar waktu.

Namun, Anda harus berhati-hati karena ada cara lain untuk DateTimeKind.Unspecifiedmenyelinap ke dalam kode Anda. Misalnya, Anda mungkin deserialize model Anda dari data JSON, dan default flavor deserializer Anda untuk jenis ini. Terserah Anda untuk berhati-hati terhadap tanggal lokal yang ditandai DateTimeKind.Unspecifieddari mendapatkan setter itu dari siapa pun kecuali EF.

staa99
sumber
6
Seperti yang saya ketahui setelah beberapa tahun bergulat dengan masalah ini, jika Anda menetapkan atau memilih bidang DateTime ke dalam struktur lain, misalnya objek transfer data, EF mengabaikan metode pengambil dan penyetel. Dalam kasus ini, Anda masih harus mengubah Jenis menjadi DateTimeKind.Utcsetelah hasil Anda dibuat. Contoh: from o in myContext.Records select new DTO() { BrokenTimestamp = o.BbTimestamp };menyetel semua Jenis ke DateTimeKind.Unspecified.
Suncat2000
1
Saya telah menggunakan DateTimeOffset dengan Entity Framework untuk sementara waktu dan jika Anda menentukan entitas EF Anda dengan tipe data DateTimeOffset, maka semua kueri EF Anda akan mengembalikan tanggal dengan offset dari UTC, persis seperti yang disimpan di DB. Jadi, jika Anda mengubah tipe data Anda ke DateTimeOffset daripada DateTime Anda tidak memerlukan solusi di atas.
Moutono
Senang mengetahuinya! Terima kasih @Moutono
Sesuai komentar @ Suncat2000, ini sama sekali tidak berfungsi dan harus dihapus
Ben Morris
4

Jika Anda berhati-hati untuk meneruskan tanggal UTC dengan benar saat Anda menetapkan nilai dan semua yang Anda pedulikan adalah memastikan DateTimeKind diatur dengan benar saat entitas diambil dari database, lihat jawaban saya di sini: https://stackoverflow.com/ a / 9386364/279590

michael.aird
sumber
3

Setahun lagi, solusi lain! Ini untuk EF Core.

Saya memiliki banyak DATETIME2(7)kolom yang dipetakan DateTime, dan selalu menyimpan UTC. Saya tidak ingin menyimpan offset karena jika kode saya benar maka offset akan selalu nol.

Sementara itu saya memiliki kolom lain yang menyimpan nilai tanggal-waktu dasar dari offset yang tidak diketahui (disediakan oleh pengguna), jadi mereka hanya disimpan / ditampilkan "sebagaimana adanya", dan tidak dibandingkan dengan apa pun.

Oleh karena itu saya membutuhkan solusi yang dapat saya terapkan pada kolom tertentu.

Tentukan metode ekstensi 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));
}

Ini kemudian dapat digunakan pada properti dalam pengaturan model:

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

Ini memiliki keuntungan kecil atas atribut yang hanya dapat Anda terapkan ke properti dengan tipe yang benar.

Perhatikan bahwa ini mengasumsikan nilai-nilai dari DB dalam UTC tetapi hanya salah Kind. Oleh karena itu, ini mengatur nilai yang Anda coba simpan di DB, memberikan pengecualian deskriptif jika bukan UTC.

Daniel Earwicker
sumber
1
Ini adalah solusi hebat yang seharusnya lebih tinggi terutama sekarang karena sebagian besar pengembangan baru akan menggunakan Core atau .NET 5. Poin imajiner bonus untuk kebijakan penegakan UTC - jika lebih banyak orang mempertahankan tanggal UTC hingga tampilan pengguna yang sebenarnya, kami hampir tidak memiliki bug tanggal / waktu.
oflahero
1

Bagi mereka yang perlu mencapai solusi @MattJohnson dengan .net framework 4 seperti saya, dengan batasan sintaks / metode refleksi, diperlukan sedikit modifikasi seperti yang tercantum di bawah ini:

     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
sumber
1

Solusi Matt Johnson-Pint berfungsi, tetapi jika semua DateTimes Anda seharusnya UTC, membuat atribut akan terlalu berputar-putar. Inilah cara saya menyederhanakannya:

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
sumber
0

Pendekatan lain adalah membuat antarmuka dengan properti datetime, mengimplementasikannya pada kelas entitas parsial. Dan kemudian gunakan peristiwa SavingChanges untuk memeriksa apakah objek tersebut berjenis antarmuka, setel nilai waktu itu ke apa pun yang Anda inginkan. Faktanya, jika ini dibuat pada / diubah pada jenis tanggal, Anda dapat menggunakan acara itu untuk mengisinya.

AD.Net
sumber
0

Dalam kasus saya, saya hanya memiliki satu tabel dengan waktu UTC. Inilah yang saya lakukan:

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
sumber